@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.
Files changed (38) hide show
  1. package/README.md +337 -0
  2. package/bin/install.mjs +231 -0
  3. package/package.json +44 -0
  4. package/skills/browser-automation/SKILL.md +93 -0
  5. package/skills/commit-and-release/SKILL.md +135 -0
  6. package/skills/dependency-updates/SKILL.md +102 -0
  7. package/skills/design-previews/SKILL.md +118 -0
  8. package/skills/engagement-types/SKILL.md +108 -0
  9. package/skills/investigate/SKILL.md +95 -0
  10. package/skills/project-context/SKILL.md +89 -0
  11. package/skills/quality-gates/SKILL.md +94 -0
  12. package/skills/quality-gates/references/toolchain.md +104 -0
  13. package/skills/safety-hooks/SKILL.md +121 -0
  14. package/skills/safety-hooks/scripts/check-destructive.sh +80 -0
  15. package/skills/safety-hooks/scripts/check-edit-boundary.sh +65 -0
  16. package/skills/support-triage/SKILL.md +103 -0
  17. package/skills/task-tracking/SKILL.md +243 -0
  18. package/skills/web-qa/SKILL.md +108 -0
  19. package/skills/web-qa/references/qa-checklist.md +98 -0
  20. package/skills/wordpress-blocks/SKILL.md +110 -0
  21. package/skills/wordpress-blocks/references/block-grammar.md +94 -0
  22. package/skills/wordpress-blocks/references/core-blocks.md +123 -0
  23. package/skills/wordpress-blocks/references/patterns-and-parts.md +70 -0
  24. package/skills/wordpress-blocks/references/recipes/faq.md +56 -0
  25. package/skills/wordpress-blocks/references/recipes/hero.md +74 -0
  26. package/skills/wordpress-blocks/references/recipes/pricing-table.md +77 -0
  27. package/skills/wordpress-blocks/references/tool-contract.md +167 -0
  28. package/skills/wordpress-blocks/references/validation.md +38 -0
  29. package/skills/wp-audit/SKILL.md +115 -0
  30. package/skills/wp-block-conventions/SKILL.md +134 -0
  31. package/skills/wp-block-conventions/references/block-anatomy.md +175 -0
  32. package/skills/wp-implementation-choice/SKILL.md +88 -0
  33. package/skills/wp-local-setup/SKILL.md +262 -0
  34. package/skills/wp-pressable/SKILL.md +172 -0
  35. package/skills/wp-studio-cli/SKILL.md +165 -0
  36. package/skills/write-a-linchpin-skill/SKILL.md +195 -0
  37. package/skills/write-a-linchpin-skill/references/template.md +83 -0
  38. package/upstream.json +20 -0
@@ -0,0 +1,243 @@
1
+ ---
2
+ name: task-tracking
3
+ description: Associate every unit of work with a task in Linchpin's task platform (currently ClickUp, via the ClickUp MCP) with the least possible friction, update that task when the work lands, and leave a handoff on it when stopping mid-flight. Use whenever starting work, creating a TODO, preparing to commit, finishing a change, or pausing work someone else may pick up. Resolve a ClickUp task from what the user gave you (ID/custom-ID/URL) or by searching; if none exists, confirm NO-TASK and keep working. The conventional-commit scope carries the task key (e.g. LINCHPIN-5113) or NO-TASK.
4
+ version: 1.2.0
5
+ ---
6
+
7
+ # Task tracking (ClickUp)
8
+
9
+ **Goal: get work into the task system with the least friction, while never blocking the
10
+ user from working.** Every unit of work is either tied to a ClickUp task (its commits carry
11
+ the task key) or explicitly marked **NO-TASK**. The user always chooses; you make the
12
+ right thing the easy thing.
13
+
14
+ The platform is currently **ClickUp**, driven through the **ClickUp MCP**. Keep ClickUp
15
+ specifics in the tool calls; the workflow below is what matters and would survive a
16
+ platform change.
17
+
18
+ ## When to use
19
+
20
+ - Starting any unit of work, before cutting a branch.
21
+ - Opening a local TODO that should exist in the task system too.
22
+ - Preparing to commit and needing the scope key.
23
+ - Finishing work — the task needs its status and a pointer to the PR.
24
+
25
+ **Not this skill:** the commit message grammar and release rules —
26
+ [`commit-and-release`](../commit-and-release/SKILL.md). Running checks before you commit —
27
+ [`quality-gates`](../quality-gates/SKILL.md).
28
+
29
+ ## Owns
30
+
31
+ Canonical for: resolving, creating, and updating the task; the **scope key** that goes in
32
+ commits; branch naming; and the PR ↔ task link. Everything about the commit message *other
33
+ than the scope* belongs to [`commit-and-release`](../commit-and-release/SKILL.md).
34
+
35
+ ## Vocabulary
36
+
37
+ - **Task key / issue key** — ClickUp's *custom ID*, e.g. `LINCHPIN-5113`. Space-scoped, so
38
+ prefixes differ per space (`LINCHPIN-…`, etc.). This is what goes in commits. It is **not**
39
+ the internal id (`86badg2te`) — though `clickup_get_task` accepts either.
40
+ - **NO-TASK** — the sentinel used in the commit scope when work has no associated task.
41
+
42
+ ## Workflow
43
+
44
+ ### 1. Resolve the task (at the start of a unit of work)
45
+
46
+ Try these in order; stop at the first that succeeds:
47
+
48
+ 1. **User gave a reference** (a custom ID like `LINCHPIN-5113`, an internal id, or an
49
+ `app.clickup.com/t/...` URL) → `clickup_get_task` to confirm it exists. Capture its
50
+ `custom_id`, `name`, and `status`. Done.
51
+ 2. **No reference given → search before asking.** Derive keywords from the work and
52
+ `clickup_search` (or `clickup_filter_tasks`), narrowed to the relevant Space/List so you
53
+ don't trawl all 36 spaces — infer the Space from the repo (e.g. repo `linchpin.com` →
54
+ Space **Linchpin**). Prefer open/active statuses. If there are plausible matches, present
55
+ the top few and let the user pick or reject.
56
+ 3. **Still nothing → ask, don't assume.** Use `AskUserQuestion`:
57
+ - **Work as NO-TASK** (recommended for quick/throwaway work) — proceed now, commits use
58
+ `NO-TASK`.
59
+ - **Create a task now** — run the creation flow below, then use the new key.
60
+ - The user can also paste a key they had in mind.
61
+
62
+ **Never block work.** NO-TASK is a first-class, always-available choice.
63
+
64
+ Record the resolved key (or `NO-TASK`) for this unit of work and reuse it for every commit
65
+ in it. When you open a local TODO with `TaskCreate`, put the ClickUp key (or `NO-TASK`) in
66
+ the task text so the local list and ClickUp stay aligned. Then cut the working branch for
67
+ this change — see **Branch & PR**.
68
+
69
+ ### 2. Before committing, if NO-TASK — offer to create one
70
+
71
+ When the work is complete and you're about to commit a NO-TASK change, ask once (via
72
+ `AskUserQuestion`): **"Create a ClickUp task for this before committing?"**
73
+
74
+ - **No** → commit with `NO-TASK`.
75
+ - **Yes** → run the creation flow, then commit with the new key.
76
+
77
+ Don't ask repeatedly and don't nag — one prompt at commit time.
78
+
79
+ ### 3. Creation flow (least friction)
80
+
81
+ `clickup_create_task` requires a `list_id` and `name`. Resolve the list with the cheapest
82
+ path that works:
83
+
84
+ 1. **Use a known default if the project has one.** If the project pins a default list
85
+ (see *Reducing friction*), confirm it in one line ("Create in *Linchpin › linchpin.com ›
86
+ Development*?") rather than making the user navigate.
87
+ 2. **Otherwise present a picker** built from `clickup_get_workspace_hierarchy`:
88
+ - Call it with `max_depth: 2` **scoped to the likely Space** (pass `space_ids`) so you
89
+ return Folders + Lists for one space, not the whole workspace.
90
+ - Offer the candidate Lists via `AskUserQuestion`. Example (Linchpin space):
91
+ `linchpin.com › Development`, `linchpin.com › Deploy`, `Internal Projects › …`.
92
+ - Only widen to a Space picker first if you can't infer the Space.
93
+ 3. **Name + details:** default the task `name` to the commit subject / work summary; confirm
94
+ or let the user tweak. Optionally set `assignees: ["me"]` (resolve via
95
+ `clickup_resolve_assignees`), a `priority`, and a short `markdown_description`.
96
+ 4. `clickup_create_task` → read back the new task's **`custom_id`** and use it as the commit
97
+ scope.
98
+
99
+ ### 4. Update the task when the work lands
100
+
101
+ A task that never moves is worse than no task — it makes the board lie. Once the change is
102
+ committed and the PR is open, close the loop in ClickUp:
103
+
104
+ 1. **Comment with the pointer.** `clickup_create_comment` on the task with the PR URL and a
105
+ one-line summary of what changed. This is the step that makes the task useful to whoever
106
+ picks it up next — do it even if you can't move the status.
107
+ 2. **Move the status** with `clickup_update_task` — but **read the valid statuses first**.
108
+ They're per-Space/List (`in progress`, `review`, `qa`, `complete`, … all vary); take them
109
+ from the task's own status metadata or `clickup_get_list`. If none obviously matches the
110
+ state ("PR open, awaiting review"), ask rather than guess.
111
+ 3. **Don't close what you can't verify.** Work in an open PR is *in review*, not done. Only
112
+ move a task to a terminal status when the user says it's shipped, or when a deploy you
113
+ can observe has completed.
114
+ 4. **Time tracking is opt-in.** Only use `clickup_add_time_entry` (or the start/stop tools)
115
+ when the user asked for it.
116
+
117
+ For `NO-TASK` work there's nothing to update — skip this step silently.
118
+
119
+ ### 5. Session handoff (when you stop mid-flight)
120
+
121
+ When work pauses unfinished — end of day, a context switch, or handing off — the task is
122
+ where the state belongs. Not a local file, not the chat: the task is what a teammate opens.
123
+
124
+ Post one `clickup_create_comment` with these five things, and nothing else:
125
+
126
+ ```markdown
127
+ **Handoff — <date>**
128
+ - **Done:** <what actually works now, and where it's committed or pushed>
129
+ - **In progress:** <what's half-built, and which files>
130
+ - **Open decisions:** <what needs an answer before continuing, and the options>
131
+ - **Where:** branch `<branch>`, PR <link or "none yet">, environment <local|staging|prod>
132
+ - **Next step:** <the single next action, concrete enough to start from cold>
133
+ ```
134
+
135
+ Rules that keep this useful:
136
+
137
+ - **Write it for someone who wasn't here.** No "as discussed", no pronouns pointing at chat
138
+ history.
139
+ - **Be honest about what's unfinished.** A handoff that overstates progress costs more than
140
+ no handoff.
141
+ - **One handoff comment per pause**, not a running log — supersede the previous one by
142
+ posting a fresh comment rather than editing history.
143
+ - Uncommitted work is not a handoff. Commit or stash it first and say which
144
+ ([`commit-and-release`](../commit-and-release/SKILL.md)).
145
+
146
+ ## Conventional commits
147
+
148
+ **Scope = the task key or `NO-TASK`.** That is this skill's half of the message:
149
+
150
+ ```
151
+ feat(LINCHPIN-5113): Add cloudflare email sending on launch
152
+ chore(NO-TASK): Tidy editorconfig and ignore rules
153
+ ```
154
+
155
+ Everything else about the message — allowed types (they differ per repo), sentence case,
156
+ punctuation limits, breaking changes — lives in
157
+ [`commit-and-release`](../commit-and-release/SKILL.md). Two notes that are about the *key*
158
+ rather than the grammar:
159
+
160
+ - A trailing PR number — `… (#758)` — is appended by the PR/release flow; you don't add it
161
+ by hand.
162
+ - Reserve `chore(main): …` for release-please release commits — don't reuse `main` as a
163
+ scope for normal work.
164
+
165
+ ## Branch & PR
166
+
167
+ Work happens on a dedicated branch opened as a PR against the base branch (usually `main`)
168
+ — never commit straight to `main`.
169
+
170
+ - **Branch naming** (cut from an up-to-date `main`):
171
+ - With a task: **`issue/<ISSUE-KEY>`** — the ClickUp custom ID, e.g. `issue/LINCHPIN-5113`.
172
+ - NO-TASK: **`no-task/<short-kebab-slug>`** describing the change (a bare `issue/no-task`
173
+ would collide across changes).
174
+ - Started NO-TASK, then created a task before pushing? Rename the branch to match:
175
+ `git branch -m issue/<ISSUE-KEY>`.
176
+ - **Pull request** (head = your branch, base = `main`):
177
+ - Title follows the commit convention: `type(<ISSUE-KEY | NO-TASK>): subject`.
178
+ - Body **links the ClickUp task** — paste `https://app.clickup.com/t/<ISSUE-KEY>` and the
179
+ key so GitHub ↔ ClickUp stay connected. For NO-TASK, note there's no task.
180
+ - Keep every commit on the branch using the same scope.
181
+
182
+ ## Reducing friction
183
+
184
+ - **Pin a default list per project.** Record the project's usual Space/List (id + path) in
185
+ the project's `CLAUDE.md` or a small `.clickup.json`, so creation becomes a one-line
186
+ confirm instead of navigation — e.g. `<Space> › <Project> › Development` with its
187
+ `list_id`. The id belongs in that project's repo, not in this shared library.
188
+ - **Remember the last-used list** within a session and reuse it.
189
+ - **Infer the Space from the repo** to scope every search and hierarchy call.
190
+ - **Batch the questions**: when you must ask, resolve task-vs-NO-TASK and (if creating) the
191
+ list in as few `AskUserQuestion` prompts as possible.
192
+
193
+ ## Gotchas
194
+
195
+ - **Search before creating** — avoid duplicate tasks; an open task often already exists.
196
+ - **Don't dump the hierarchy.** 36 spaces is overwhelming; always scope `space_ids` and go
197
+ only as deep as you need (`max_depth`).
198
+ - **Use `custom_id` in commits**, never the internal id (`86badg2te`).
199
+ - **Custom-ID prefixes are space-specific** — read the prefix off the task; don't assume
200
+ `LINCHPIN-` for non-Linchpin work.
201
+ - **NO-TASK is not failure.** Offer task creation, accept "no", and move on.
202
+
203
+ ## Quick reference
204
+
205
+ | Step | Tool |
206
+ | --- | --- |
207
+ | Confirm a given key/URL | `clickup_get_task` (accepts `LINCHPIN-5113` or `86badg2te`) |
208
+ | Find an existing task | `clickup_search` / `clickup_filter_tasks` (scope to the Space) |
209
+ | Build a Space/List picker | `clickup_get_workspace_hierarchy` (`space_ids`, `max_depth: 2`) |
210
+ | Resolve "me"/assignees | `clickup_resolve_assignees` |
211
+ | Create the task | `clickup_create_task` (`list_id` + `name` required) |
212
+ | Branch the work | `git switch -c issue/LINCHPIN-#### main` (or `no-task/<slug>`) |
213
+ | Commit | `type(LINCHPIN-#### | NO-TASK): subject` |
214
+ | Open the PR | `gh pr create` — title `type(KEY): …`, body links `app.clickup.com/t/<KEY>` |
215
+ | Comment on the task | `clickup_create_comment` (PR URL + one-line summary) |
216
+ | Move the status | `clickup_update_task` (valid statuses come from the List) |
217
+ | Hand off mid-flight | `clickup_create_comment` with the five-line handoff block |
218
+
219
+ Where a *new* task belongs — which space, folder, and (on multi-site clients) which site —
220
+ is decided by [`engagement-types`](../engagement-types/SKILL.md).
221
+
222
+ ## Guardrails
223
+
224
+ - **Never invent a task key.** If you can't resolve one, `NO-TASK` is the correct answer.
225
+ - **Never block the user** waiting for a task decision — NO-TASK is always available.
226
+ - **Never mark a task complete** on your own judgment. An open PR is at most "in review";
227
+ terminal statuses need the user's word or an observed deploy.
228
+ - **Never guess a status name** — they're Space-specific. Read them, then set them.
229
+ - **Never create a duplicate task** — search first; an open one usually exists.
230
+ - **Never dump the full workspace hierarchy** into a prompt; scope `space_ids` and
231
+ `max_depth`.
232
+ - Don't log time unless asked.
233
+
234
+ ## Done
235
+
236
+ - [ ] The unit of work has a resolved task key or an explicit, user-accepted `NO-TASK`.
237
+ - [ ] The branch name matches the key (`issue/<KEY>` or `no-task/<slug>`).
238
+ - [ ] Every commit on the branch carries the same scope.
239
+ - [ ] The PR body links `app.clickup.com/t/<KEY>` (or notes there is no task).
240
+ - [ ] For task-backed work: a comment with the PR link exists, and the status reflects
241
+ reality without over-claiming completion.
242
+ - [ ] If work is pausing unfinished, a handoff comment exists that a teammate could act on
243
+ cold, and nothing is left uncommitted.
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: web-qa
3
+ description: QA a site or app like a real user and fix what you find — explore the front end, wp-admin, and the block editor in a browser, record findings with severity and evidence, then fix each bug in its own atomic commit and re-verify. Use when asked to QA, test, or "make sure this works", after shipping a feature to a branch, before handing work to a client, or when you want a bug report without fixes (report-only mode). Not for lint or unit tests — use `quality-gates`.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Web QA
8
+
9
+ You are the QA engineer *and* the fix engineer. Click everything, fill every form, check
10
+ every state — then fix what's broken, one atomic commit at a time, and prove it with
11
+ before/after evidence.
12
+
13
+ The discipline that makes this useful rather than noise: **scope it, evidence it, fix it in
14
+ isolation, re-verify it.** A QA pass that ends in one giant commit is not reviewable, and a
15
+ finding without evidence is an opinion.
16
+
17
+ ## When to use
18
+
19
+ - "QA this", "test this", "make sure this works", "does this actually run?"
20
+ - After shipping a feature to a branch, before opening or merging the PR.
21
+ - Before handing work back to a client.
22
+ - When you want findings only, no code changes — say so, and it runs report-only.
23
+
24
+ **Not this skill:** lint, PHPCS, static analysis, unit tests —
25
+ [`quality-gates`](../quality-gates/SKILL.md). Performance and accessibility measurement —
26
+ [`wp-audit`](../wp-audit/SKILL.md). Root-causing one specific bug —
27
+ [`investigate`](../investigate/SKILL.md).
28
+
29
+ ## Owns
30
+
31
+ Canonical for: QA scope and tiers, the explore → triage → fix → verify loop, severity, and
32
+ the report format. Browser mechanics belong to
33
+ [`browser-automation`](../browser-automation/SKILL.md); commits to
34
+ [`commit-and-release`](../commit-and-release/SKILL.md).
35
+
36
+ ## Preflight
37
+
38
+ 1. **Orient** via [`project-context`](../project-context/SKILL.md) — repo shape, environment,
39
+ URL, host.
40
+ 2. **Require a clean working tree.** `git status --porcelain` must be empty, so each fix
41
+ lands as its own reviewable commit. If it isn't, stop and offer: commit the current work,
42
+ stash it, or abort. Never QA on top of uncommitted changes.
43
+ 3. **Resolve scope**, in this order:
44
+ - The user named a URL or area → use it.
45
+ - On a feature branch with no URL → **diff-aware mode**: `git diff --name-only` against
46
+ the merge base, and test what those files affect. This is the common case.
47
+ - Otherwise → the whole app, front end and admin.
48
+ 4. **Set the tier** — it decides what gets fixed, not what gets reported:
49
+
50
+ | Tier | Fixes | Use for |
51
+ | --- | --- | --- |
52
+ | Quick | Critical + high | A fast pre-PR check |
53
+ | **Standard** (default) | + medium | Normal QA |
54
+ | Exhaustive | + low/cosmetic | Pre-client-handoff polish |
55
+ | Report-only | nothing | You want the findings, not the changes |
56
+
57
+ ## Procedure
58
+
59
+ 1. **Explore like a user, not like a developer.** Front end *and* `/wp-admin` *and* the block
60
+ editor where content is involved; primary flows first, then forms, empty states, error
61
+ states, and mobile width. See
62
+ [`references/qa-checklist.md`](references/qa-checklist.md).
63
+ → Every area in scope has been visited, not assumed.
64
+ 2. **Record each finding with evidence** — what you did, what happened, what should have
65
+ happened, plus a screenshot and any console or network error. No evidence, no finding.
66
+ → A reader could reproduce it without you.
67
+ 3. **Assign severity** from the table in
68
+ [`references/qa-checklist.md`](references/qa-checklist.md), and say plainly when something
69
+ is a judgment call rather than a defect. → Each finding is critical/high/medium/low.
70
+ 4. **Triage against the tier.** List what you're fixing and what you're leaving, before
71
+ touching code. → The user can redirect before any commits exist.
72
+ 5. **Fix loop — one bug at a time.** Fix in the source (never in the database or on the
73
+ server), run [`quality-gates`](../quality-gates/SKILL.md), re-test that specific flow, then
74
+ commit that single fix per
75
+ [`commit-and-release`](../commit-and-release/SKILL.md). → One bug, one commit, one
76
+ verification.
77
+ 6. **Final pass.** Re-run the primary flows end to end to catch anything the fixes broke.
78
+ → Green, or a new finding that goes back through triage.
79
+ 7. **Report** — what was tested, findings by severity with before/after evidence, what was
80
+ fixed, what was deliberately left, and what couldn't be tested. Then update the ClickUp
81
+ task ([`task-tracking`](../task-tracking/SKILL.md)).
82
+
83
+ ## Guardrails
84
+
85
+ - **Never start on a dirty tree.** Mixing QA fixes with in-flight work makes both unreviewable.
86
+ - **Never batch unrelated fixes into one commit** — a single bad fix then can't be reverted
87
+ without taking the good ones with it.
88
+ - **Never fix a bug by changing the database or editing files on the server.** Code fixes go
89
+ through the repo and the deploy pipeline ([`wp-pressable`](../wp-pressable/SKILL.md)).
90
+ - **Never QA destructively against production.** Deletes, bulk actions, payments, and "send"
91
+ buttons are real there. Test locally or on staging; if production is the only option, it's
92
+ read-only and confirmed first.
93
+ - **Never claim a fix works without re-testing the exact flow that failed.** "Should work now"
94
+ is not verification.
95
+ - **Never expand scope silently.** A refactor discovered mid-QA is a finding, not a fix.
96
+ - **Never report a clean pass for an area you couldn't reach** — auth walls, missing test
97
+ data, and unreachable states get listed as untested, not as passing.
98
+
99
+ ## Done
100
+
101
+ - [ ] Started from a clean tree; scope and tier stated up front.
102
+ - [ ] Every in-scope area actually exercised in a browser, including admin where relevant.
103
+ - [ ] Findings carry reproduction steps, severity, and evidence.
104
+ - [ ] Triage shown to the user before fixes began.
105
+ - [ ] Each fix is its own commit, gates green, and the specific flow re-tested.
106
+ - [ ] Final end-to-end pass run after the last fix.
107
+ - [ ] Report lists fixed, deliberately-not-fixed, and untested areas.
108
+ - [ ] ClickUp task updated with the outcome.
@@ -0,0 +1,98 @@
1
+ # QA checklist, severity, and report format
2
+
3
+ ## Severity
4
+
5
+ Severity is about **user impact**, not how hard it is to fix.
6
+
7
+ | Severity | Means | Examples |
8
+ | --- | --- | --- |
9
+ | **Critical** | Blocks a core task, loses data, or exposes something it shouldn't | Checkout fails, form submits nothing, 500 on a key page, private content public, fatal PHP error |
10
+ | **High** | A primary flow is broken or badly degraded, no reasonable workaround | Nav broken on mobile, search returns nothing, images 404, admin screen unusable |
11
+ | **Medium** | Works but wrong or awkward; a workaround exists | Validation message never clears, wrong date format, layout breaks at one width, confusing empty state |
12
+ | **Low** | Cosmetic or minor polish | Spacing inconsistency, a typo, a hover state that doesn't match its neighbors |
13
+
14
+ Two rules that keep reports honest:
15
+
16
+ - **A judgment call is not a defect.** "I'd have used more spacing here" is design feedback —
17
+ label it as such, don't file it as a bug.
18
+ - **Uncertain severity rounds down**, and say why. Inflating severity to force a fix wastes
19
+ the tier system.
20
+
21
+ ## What to exercise
22
+
23
+ ### Every project
24
+
25
+ - **Primary flows first** — whatever the site exists to do: contact, apply, donate, buy,
26
+ search, read.
27
+ - **Forms**: submit valid, submit empty, submit invalid. Check the error messages, the
28
+ success state, and that the submission actually arrived somewhere.
29
+ - **States**: empty, loading, error, and "lots of content". Empty and error states are where
30
+ most bugs hide because nobody looks at them.
31
+ - **Mobile width** — not just a narrow desktop window; check tap targets and any nav drawer.
32
+ - **Console and network**: JS errors, 404s, failed requests, mixed content.
33
+ - **Back/forward and refresh** mid-flow — state that only works forwards is a real bug.
34
+
35
+ ### WordPress
36
+
37
+ - **Logged out vs logged in.** The admin bar shifts layout, and capability-gated UI only
38
+ appears for some roles. Test both.
39
+ - **`/wp-admin`**: the screens this work touches — post lists, editors, settings pages,
40
+ custom post types, taxonomies.
41
+ - **Block editor**: insert the block, edit its attributes, save, reload, and confirm no
42
+ "unexpected or invalid content" ([`wordpress-blocks`](../../wordpress-blocks/SKILL.md);
43
+ `validate_blocks` via [`wp-studio-cli`](../../wp-studio-cli/SKILL.md)).
44
+ - **Content edge cases**: a very long title, a missing featured image, an unset optional
45
+ field, a post with no terms.
46
+ - **Permalinks and 404s** — a template change can quietly break the 404 or archive views.
47
+ - **Caching** — on a hosted environment, flush object and page cache before concluding a fix
48
+ didn't work.
49
+
50
+ ### Cloudflare Workers / API projects
51
+
52
+ - Route behavior and status codes, error payload shape, auth boundaries, and what happens on
53
+ a cold start. The browser matters less; the responses matter more.
54
+
55
+ ## Diff-aware mode
56
+
57
+ On a feature branch with no URL given, scope from the change itself:
58
+
59
+ ```bash
60
+ git diff --name-only $(git merge-base HEAD origin/main)...HEAD
61
+ ```
62
+
63
+ Map files to surfaces before exploring:
64
+
65
+ | Changed | Test |
66
+ | --- | --- |
67
+ | `themes/*/templates/*`, `parts/*` | The pages using that template or part, logged out and in |
68
+ | `blocks/src/<block>/*` | Insert and edit the block, save, reload, then view it on the front end |
69
+ | `plugins/*/includes/*` | The admin screens and endpoints that code backs |
70
+ | `theme.json`, `style.scss` | Visual regression across a few representative pages |
71
+ | REST or API code | The endpoints directly, plus whatever consumes them |
72
+
73
+ ## Report format
74
+
75
+ ```markdown
76
+ ## QA report — <scope> (<environment>)
77
+
78
+ **Tested:** <areas, URLs, viewports> · **Tier:** <quick|standard|exhaustive|report-only>
79
+
80
+ ### Findings
81
+
82
+ | # | Severity | Finding | Status |
83
+ |---|----------|---------|--------|
84
+ | 1 | Critical | Contact form submits but sends no email | Fixed — <commit> |
85
+ | 2 | Medium | Validation error persists after correcting the field | Fixed — <commit> |
86
+ | 3 | Low | Footer spacing differs from header | Not fixed (below tier) |
87
+
88
+ ### 1. Contact form submits but sends no email
89
+ **Steps:** … **Expected:** … **Actual:** …
90
+ **Evidence:** <screenshot>, console error, failed request
91
+ **Cause:** … **Fix:** <commit> **Verified:** re-submitted, message received
92
+
93
+ ### Not tested
94
+ - Member dashboard — no test account available
95
+ ```
96
+
97
+ Always include the **Not tested** section, even when empty. An unreachable area silently
98
+ omitted reads as an area that passed.
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: wordpress-blocks
3
+ description: Author and edit WordPress content as valid Gutenberg block markup from a chat interface (Mantle). Use whenever a request means adding or changing page/post/content — a section, hero, pricing table, FAQ, CTA, columns, etc. Encodes the pattern-first procedure (reuse the site's synced patterns / template parts / registered patterns BEFORE composing from core blocks), the block-markup grammar models get wrong, and the live tool contract the host plugin must expose. Always validate generated block markup before inserting — invalid comment delimiters silently break the editor.
4
+ version: 0.1.0
5
+ ---
6
+
7
+ # WordPress Blocks (content authoring)
8
+
9
+ Use this skill when you are **editing WordPress content** (a page, post, or other
10
+ block-based content) through a chat interface — i.e. the Mantle plugin asks you to
11
+ "add a pricing table," "build a hero," "insert an FAQ," "make this two columns," etc.
12
+
13
+ Your output for a content region is **WordPress block markup** (Gutenberg "block grammar"),
14
+ not Markdown and not plain HTML. Block markup is HTML wrapped in `<!-- wp:… -->` comment
15
+ delimiters; if those delimiters are malformed, the editor flags the content as an
16
+ **"unexpected or invalid"** block and the user loses the layout. Getting the grammar right,
17
+ and reusing the site's own design assets, is the whole job.
18
+
19
+ ## When to use
20
+
21
+ - A request means adding or changing content on a page or post — a hero, pricing table,
22
+ FAQ, CTA, columns, or any section.
23
+ - Existing content needs restructuring into different blocks.
24
+ - You need to know whether a layout should reuse a site pattern or be composed fresh.
25
+
26
+ **Not this skill:** building blocks, plugins, or themes (PHP `block.json`, `render.php`,
27
+ registering patterns in a theme) — use the upstream WordPress skills
28
+ [`wp-block-development`](https://github.com/WordPress/agent-skills) and
29
+ [`wp-block-themes`](https://github.com/WordPress/agent-skills). One site's specific block
30
+ and pattern conventions belong in that project's own `CLAUDE.md`/`AGENTS.md`.
31
+
32
+ ## The core principle: reuse before you build
33
+
34
+ A site is not a blank canvas. It already ships design assets the user (or their designer)
35
+ made, and reusing them is almost always better than hand-composing core blocks:
36
+
37
+ | Asset | What it is | How you emit it |
38
+ | --- | --- | --- |
39
+ | **Synced pattern** (`wp_block` CPT) | A saved, *linked* block — edits propagate everywhere it's used | `<!-- wp:block {"ref":ID} /-->` |
40
+ | **Registered pattern** (theme/plugin) | A reusable layout *template*, inserted as a one-time copy | Its expanded block markup (fetch via `get_pattern`) |
41
+ | **Template part** (FSE) | Header/footer/section parts of a block theme | `<!-- wp:template-part {"slug":"…","theme":"…"} /-->` (templates, not post body) |
42
+ | **Core / registered blocks** | The block library primitives | Composed markup — the **fallback** |
43
+
44
+ See `references/patterns-and-parts.md` for the distinctions and when each is appropriate.
45
+
46
+ ## Procedure (pattern-first)
47
+
48
+ When a request means "add/build/change a layout or section":
49
+
50
+ 1. **Classify the intent** and extract search keywords (e.g. "pricing table" → `pricing`,
51
+ `plans`, `tiers`).
52
+ 2. **Search the live site first.** Call `search_patterns(query)` (it covers synced +
53
+ registered patterns and template parts). Do this *before* writing any markup.
54
+ 3. **If there's a strong match**, reuse it:
55
+ - **Synced pattern** → emit `<!-- wp:block {"ref":ID} /-->`. Don't inline its markup — keep it synced. If the user wants edits to *this instance only*, detach (see `references/patterns-and-parts.md`).
56
+ - **Registered pattern** → `get_pattern(slug)` and insert its `block_markup`, adapting the copy/text to the request.
57
+ - **Template part** → only when editing a template (not post body).
58
+ 4. **If no good match**, compose from core blocks:
59
+ - Pull the recipe from `references/recipes/` when one exists (`pricing-table.md`, `hero.md`, `faq.md`); otherwise apply `references/core-blocks.md`.
60
+ - Use the site's **design tokens** (color slugs, font-size slugs, `contentSize`/`wideSize`) from `get_theme_tokens()` instead of hardcoded hex/px, so content matches the design system.
61
+ - If you reference a **non-core** block, confirm it's actually registered on this install via `list_registered_blocks()` first.
62
+ 5. **Validate before returning.** Run the markup through `validate_blocks` (or the host's
63
+ block validator) and repair/simplify on failure. See `references/validation.md`. Never
64
+ hand back content you haven't validated.
65
+
66
+ ## Output rules
67
+
68
+ - Emit **only** block markup for the content region — no Markdown fences, no commentary
69
+ mixed in. The plugin inserts your output verbatim.
70
+ - **Preserve** existing blocks unless the user asked to replace them. When editing, return
71
+ the changed block(s) in context, not the whole document, unless asked.
72
+ - **Prefer the fewest blocks** that achieve the layout. Don't wrap everything in nested
73
+ groups "just in case."
74
+ - Prefer **token references** (`{"textColor":"primary"}`, `{"fontSize":"large"}`) over raw
75
+ values when `get_theme_tokens()` exposes a matching slug.
76
+ - Core blocks drop the namespace: `wp:paragraph`, **not** `wp:core/paragraph`.
77
+
78
+ ## Reference files (load as needed)
79
+
80
+ - `references/block-grammar.md` — the delimiter grammar and the top causes of "invalid block."
81
+ - `references/core-blocks.md` — copy-paste-correct markup for the common core blocks.
82
+ - `references/patterns-and-parts.md` — synced vs registered patterns vs template parts.
83
+ - `references/tool-contract.md` — JSON Schemas for the live tools the host plugin must expose.
84
+ - `references/validation.md` — the validate-before-insert contract and repair loop.
85
+ - `references/recipes/` — fallback compositions (`pricing-table.md`, `hero.md`, `faq.md`).
86
+
87
+ ## Guardrails
88
+
89
+ - **Never return unvalidated block markup.** Malformed `<!-- wp:… -->` delimiters produce an
90
+ "unexpected or invalid content" block and the user loses their layout — validate, repair,
91
+ and only then hand it back.
92
+ - **Never inline a synced pattern's markup** to make an edit. That silently detaches it from
93
+ every other place it's used; detach deliberately or edit the pattern itself.
94
+ - **Never replace content the user didn't ask you to change.** Return the changed blocks in
95
+ context; preserve everything else exactly.
96
+ - **Never reference a block that isn't registered on the install** — confirm with
97
+ `list_registered_blocks()` before using anything outside core.
98
+ - **Never hardcode colors, sizes, or widths** when `get_theme_tokens()` exposes a matching
99
+ slug; raw values break the site's design system on theme changes.
100
+ - Don't wrap output in Markdown fences or mix commentary into it — the host inserts your
101
+ output verbatim.
102
+
103
+ ## Done
104
+
105
+ - [ ] The site was searched for an existing pattern/part before composing anything new.
106
+ - [ ] Output is block markup only — no fences, no commentary.
107
+ - [ ] Design tokens used wherever the theme exposes a matching slug.
108
+ - [ ] Every non-core block referenced is confirmed registered.
109
+ - [ ] `validate_blocks` (or the host validator) passes on the final markup.
110
+ - [ ] Untouched blocks are byte-for-byte preserved.
@@ -0,0 +1,94 @@
1
+ # Block grammar — the part models get wrong
2
+
3
+ WordPress stores block content as HTML annotated with **HTML-comment delimiters**. The
4
+ parser ("block grammar") reads those comments to know where each block starts/ends and what
5
+ its attributes are. Malformed delimiters → the editor shows *"This block contains unexpected
6
+ or invalid content"* and the user loses the layout. This file is the contract for emitting
7
+ correct markup.
8
+
9
+ Upstream: <https://developer.wordpress.org/block-editor/reference-guides/block-api/block-grammar/>
10
+ and block attributes: <https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/>
11
+
12
+ ## Anatomy of a block
13
+
14
+ ```
15
+ <!-- wp:NAMESPACE/NAME {"attr":"value"} -->
16
+ ...the block's saved HTML...
17
+ <!-- /wp:NAMESPACE/NAME -->
18
+ ```
19
+
20
+ - **Core blocks omit the namespace**: `wp:paragraph`, `wp:heading`, `wp:columns` — NOT
21
+ `wp:core/paragraph`. Third-party blocks keep theirs: `wp:linchpin/testimonial`.
22
+ - The **opening** delimiter may carry a JSON attributes object. The **closing** delimiter
23
+ never does: `<!-- /wp:paragraph -->`.
24
+ - The name in the closing delimiter must match the opening one exactly.
25
+
26
+ ## Two block forms
27
+
28
+ **1. Block with content** (open + close, inner HTML between):
29
+
30
+ ```
31
+ <!-- wp:paragraph -->
32
+ <p>Hello.</p>
33
+ <!-- /wp:paragraph -->
34
+ ```
35
+
36
+ **2. Void / self-closing block** (no inner HTML — note the `/-->`):
37
+
38
+ ```
39
+ <!-- wp:block {"ref":42} /-->
40
+ <!-- wp:spacer {"height":"40px"} /--> ← (spacer actually has HTML — see note)
41
+ <!-- wp:template-part {"slug":"header"} /-->
42
+ ```
43
+
44
+ The `/` before `-->` marks a void block. Use it **only** for blocks that save no inner HTML
45
+ (synced-pattern reference `wp:block`, `wp:pattern`, `wp:template-part`, `wp:post-content`,
46
+ etc.). A `wp:paragraph` is never self-closing. (Spacer *does* render a `<div>`, so it uses
47
+ the open/close form — see `core-blocks.md`.)
48
+
49
+ ## Attribute JSON rules
50
+
51
+ - Attributes are a **single valid JSON object**: double-quoted keys/strings, no trailing
52
+ commas, no comments, no single quotes.
53
+ - **Omit attributes that equal the default.** WordPress only serializes non-default
54
+ attributes. Adding `{"level":2}` to a heading (2 is the default) or inventing attributes
55
+ the block doesn't define is a common cause of invalid blocks.
56
+ - Numbers/booleans are unquoted: `{"dimRatio":50,"isStackedOnMobile":true}`.
57
+ - Many visual attributes map to generated classes/inline styles in the HTML **and** must be
58
+ declared in the JSON — they have to agree. Example: a heading's level lives in `{"level":3}`
59
+ *and* the tag must be `<h3>`. Don't set one without the other.
60
+
61
+ ## The #1 rule: saved HTML must match the block's save output
62
+
63
+ Each block has a fixed shape it serializes to. If your inner HTML doesn't match what that
64
+ block would save (wrong wrapper class, wrong tag, missing element), the editor marks it
65
+ invalid even when the delimiters are perfect. Practical consequences:
66
+
67
+ - A heading saves with `class="wp-block-heading"`: `<h2 class="wp-block-heading">…</h2>`.
68
+ - A button saves nested: `wp:buttons` → `.wp-block-buttons` → `wp:button` → `.wp-block-button`
69
+ → `<a class="wp-block-button__link wp-element-button">`.
70
+ - A list wraps each item in its own `wp:list-item` block (modern WP), not bare `<li>`.
71
+
72
+ Because these shapes **drift between WordPress/Gutenberg versions**, do not trust hand-written
73
+ markup blindly — always run it through `validate_blocks` (see `validation.md`) and let the
74
+ validator/normalizer repair class names and wrappers. Treat the examples in `core-blocks.md`
75
+ and `recipes/` as *starting structure*, not byte-perfect truth for every site.
76
+
77
+ ## Top causes of "invalid block"
78
+
79
+ 1. **Void/open mismatch** — `<!-- wp:block {"ref":42} -->` without `/`, or a `/-->` on a
80
+ block that has inner HTML.
81
+ 2. **Malformed attribute JSON** — trailing comma, single quotes, unescaped quote inside a
82
+ string. Escape inner quotes as `"` if needed.
83
+ 3. **Inner HTML doesn't match save output** — missing `wp-block-*` class, wrong tag, wrong
84
+ nesting.
85
+ 4. **Namespaced a core block** — `wp:core/paragraph` instead of `wp:paragraph`.
86
+ 5. **Mismatched open/close names** — `<!-- wp:group -->` … `<!-- /wp:columns -->`.
87
+ 6. **Markdown leaking in** — emitting `**bold**` or `- item` instead of `<strong>`/list
88
+ blocks. Block markup is HTML, never Markdown.
89
+
90
+ ## Whitespace
91
+
92
+ Keep block delimiters on their own lines and put a blank line between sibling blocks for
93
+ readability; the parser tolerates this. Inside a parent's wrapper `<div>`, child block
94
+ delimiters sit directly inside the div (see the columns example in `core-blocks.md`).