@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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Core block cheat sheet
|
|
2
|
+
|
|
3
|
+
Copy-paste-correct markup for the core blocks you'll compose with most. Shapes drift slightly
|
|
4
|
+
by WordPress version — always `validate_blocks` the result (see `validation.md`). Core blocks
|
|
5
|
+
omit the `core/` namespace.
|
|
6
|
+
|
|
7
|
+
## Text
|
|
8
|
+
|
|
9
|
+
**Paragraph**
|
|
10
|
+
```html
|
|
11
|
+
<!-- wp:paragraph -->
|
|
12
|
+
<p>Body copy goes here.</p>
|
|
13
|
+
<!-- /wp:paragraph -->
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Heading** (default level is `h2`; set `{"level":N}` AND change the tag together)
|
|
17
|
+
```html
|
|
18
|
+
<!-- wp:heading -->
|
|
19
|
+
<h2 class="wp-block-heading">Section title</h2>
|
|
20
|
+
<!-- /wp:heading -->
|
|
21
|
+
|
|
22
|
+
<!-- wp:heading {"level":3} -->
|
|
23
|
+
<h3 class="wp-block-heading">Subsection</h3>
|
|
24
|
+
<!-- /wp:heading -->
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**List** (each item is its own `wp:list-item`)
|
|
28
|
+
```html
|
|
29
|
+
<!-- wp:list -->
|
|
30
|
+
<ul class="wp-block-list"><!-- wp:list-item -->
|
|
31
|
+
<li>First</li>
|
|
32
|
+
<!-- /wp:list-item -->
|
|
33
|
+
|
|
34
|
+
<!-- wp:list-item -->
|
|
35
|
+
<li>Second</li>
|
|
36
|
+
<!-- /wp:list-item --></ul>
|
|
37
|
+
<!-- /wp:list -->
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Layout containers
|
|
41
|
+
|
|
42
|
+
**Group** (constrained = respects theme content width; also `"flex"`, `"flow"`)
|
|
43
|
+
```html
|
|
44
|
+
<!-- wp:group {"layout":{"type":"constrained"}} -->
|
|
45
|
+
<div class="wp-block-group">
|
|
46
|
+
<!-- child blocks -->
|
|
47
|
+
</div>
|
|
48
|
+
<!-- /wp:group -->
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Columns** (note nesting: `wp:columns` → `wp:column` → children)
|
|
52
|
+
```html
|
|
53
|
+
<!-- wp:columns -->
|
|
54
|
+
<div class="wp-block-columns"><!-- wp:column -->
|
|
55
|
+
<div class="wp-block-column">
|
|
56
|
+
<!-- column 1 children -->
|
|
57
|
+
</div>
|
|
58
|
+
<!-- /wp:column -->
|
|
59
|
+
|
|
60
|
+
<!-- wp:column -->
|
|
61
|
+
<div class="wp-block-column">
|
|
62
|
+
<!-- column 2 children -->
|
|
63
|
+
</div>
|
|
64
|
+
<!-- /wp:column --></div>
|
|
65
|
+
<!-- /wp:columns -->
|
|
66
|
+
```
|
|
67
|
+
Set widths with `{"width":"33.33%"}` on each `wp:column`. Stacking on mobile is on by default;
|
|
68
|
+
`{"isStackedOnMobile":false}` to disable.
|
|
69
|
+
|
|
70
|
+
**Spacer** (has inner HTML — NOT self-closing)
|
|
71
|
+
```html
|
|
72
|
+
<!-- wp:spacer {"height":"40px"} -->
|
|
73
|
+
<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>
|
|
74
|
+
<!-- /wp:spacer -->
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Media & actions
|
|
78
|
+
|
|
79
|
+
**Image** (`id` ties to a real attachment; omit it for external/placeholder `src`)
|
|
80
|
+
```html
|
|
81
|
+
<!-- wp:image {"id":123,"sizeSlug":"large","linkDestination":"none"} -->
|
|
82
|
+
<figure class="wp-block-image size-large"><img src="https://example.com/x.jpg" alt="" class="wp-image-123"/></figure>
|
|
83
|
+
<!-- /wp:image -->
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Buttons** (always wrap a `wp:button` inside `wp:buttons`)
|
|
87
|
+
```html
|
|
88
|
+
<!-- wp:buttons -->
|
|
89
|
+
<div class="wp-block-buttons"><!-- wp:button -->
|
|
90
|
+
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="/contact">Get started</a></div>
|
|
91
|
+
<!-- /wp:button --></div>
|
|
92
|
+
<!-- /wp:buttons -->
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Cover** (background image/overlay with inner content; `dimRatio` = overlay opacity)
|
|
96
|
+
```html
|
|
97
|
+
<!-- wp:cover {"url":"https://example.com/bg.jpg","dimRatio":50,"minHeight":480} -->
|
|
98
|
+
<div class="wp-block-cover" style="min-height:480px"><span aria-hidden="true" class="wp-block-cover__background has-background-dim"></span><img class="wp-block-cover__image-background" src="https://example.com/bg.jpg" alt=""/><div class="wp-block-cover__inner-container">
|
|
99
|
+
<!-- inner blocks: heading, paragraph, buttons -->
|
|
100
|
+
</div></div>
|
|
101
|
+
<!-- /wp:cover -->
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Using design tokens
|
|
105
|
+
|
|
106
|
+
Prefer the site's theme.json slugs over hardcoded values (pull them from `get_theme_tokens()`):
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<!-- wp:heading {"textColor":"primary","fontSize":"x-large"} -->
|
|
110
|
+
<h2 class="wp-block-heading has-primary-color has-text-color has-x-large-font-size">Title</h2>
|
|
111
|
+
<!-- /wp:heading -->
|
|
112
|
+
```
|
|
113
|
+
The JSON attribute (`"textColor":"primary"`) and the generated classes
|
|
114
|
+
(`has-primary-color has-text-color`) must agree — this is why validation matters.
|
|
115
|
+
|
|
116
|
+
## Reuse references (not composition)
|
|
117
|
+
|
|
118
|
+
These insert existing assets — see `patterns-and-parts.md`:
|
|
119
|
+
```html
|
|
120
|
+
<!-- wp:block {"ref":451} /--> ← synced pattern (wp_block #451)
|
|
121
|
+
<!-- wp:pattern {"slug":"linchpin/pricing-3col"} /--> ← registered pattern reference
|
|
122
|
+
<!-- wp:template-part {"slug":"footer","theme":"linchpin"} /-->
|
|
123
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Patterns and template parts — what to reuse, and how
|
|
2
|
+
|
|
3
|
+
The site already holds reusable design assets. Reusing them beats hand-composing core blocks:
|
|
4
|
+
it matches the designer's intent, stays on-brand, and (for synced patterns) keeps content
|
|
5
|
+
updatable in one place. Three distinct things get confused — here's the contract for each.
|
|
6
|
+
|
|
7
|
+
## 1. Synced patterns (`wp_block` custom post type)
|
|
8
|
+
|
|
9
|
+
The successor to "reusable blocks." A saved block stored once and **linked** wherever it's
|
|
10
|
+
used — edit it in one place, every instance updates.
|
|
11
|
+
|
|
12
|
+
- **Find:** `search_patterns(query)` returns synced patterns with a numeric `ref` (the
|
|
13
|
+
`wp_block` post ID).
|
|
14
|
+
- **Emit:** a void reference block — do **not** inline its markup:
|
|
15
|
+
```html
|
|
16
|
+
<!-- wp:block {"ref":451} /-->
|
|
17
|
+
```
|
|
18
|
+
- **Edit one instance only:** the user must *detach* it (turns it into normal blocks). If
|
|
19
|
+
they ask to "change just this one," fetch the expanded markup via `get_pattern(ref:451)`,
|
|
20
|
+
insert that instead of the reference, and tell them it's now detached.
|
|
21
|
+
- **Best for:** repeated, centrally-managed content — a standard CTA band, a contact block,
|
|
22
|
+
a promo the client updates site-wide.
|
|
23
|
+
|
|
24
|
+
## 2. Registered patterns (theme- or plugin-provided)
|
|
25
|
+
|
|
26
|
+
Reusable **layout templates** registered in code (theme `patterns/*.php`, a plugin, or the
|
|
27
|
+
Pattern Directory). Inserting one drops a **one-time copy** of its blocks — no live link.
|
|
28
|
+
|
|
29
|
+
- **Find:** `search_patterns(query)` returns them with a `slug` and `type:"registered"`.
|
|
30
|
+
- **Emit:** fetch and inline the expanded blocks, adapting the copy to the request:
|
|
31
|
+
```
|
|
32
|
+
const { block_markup } = get_pattern(slug: "linchpin/pricing-3col")
|
|
33
|
+
// insert block_markup, then swap in the user's plan names/prices
|
|
34
|
+
```
|
|
35
|
+
(Some hosts also accept the overlay form `<!-- wp:pattern {"slug":"…"} /-->`, but inlining
|
|
36
|
+
the expanded markup is the portable, editable choice.)
|
|
37
|
+
- **Best for:** starting a section from a designed layout the user will then customize.
|
|
38
|
+
|
|
39
|
+
## 3. Template parts (`wp_template_part` — block themes / FSE)
|
|
40
|
+
|
|
41
|
+
Header, footer, and reusable section parts of a **block theme**. These belong to *templates*,
|
|
42
|
+
not to a page/post body.
|
|
43
|
+
|
|
44
|
+
- **Find:** `list_template_parts()` → `{slug, title, area, theme}`.
|
|
45
|
+
- **Emit (templates only):**
|
|
46
|
+
```html
|
|
47
|
+
<!-- wp:template-part {"slug":"footer","theme":"linchpin","tagName":"footer"} /-->
|
|
48
|
+
```
|
|
49
|
+
- **Do not** insert a template part into post/page content. If the user wants a footer-like
|
|
50
|
+
block *inside* a page, use a synced pattern or compose blocks instead.
|
|
51
|
+
|
|
52
|
+
## How this maps to the pattern-first procedure
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
request → search_patterns(keywords)
|
|
56
|
+
├─ synced match? → <!-- wp:block {"ref":ID} /-->
|
|
57
|
+
├─ registered match? → get_pattern(slug) → insert expanded markup, adapt copy
|
|
58
|
+
├─ template part? → only if editing a template → wp:template-part
|
|
59
|
+
└─ no match → compose core blocks (recipes/ + core-blocks.md) using get_theme_tokens()
|
|
60
|
+
→ validate_blocks → insert
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Prefer a **good** match over a forced one. A weak pattern match that needs heavy rewriting is
|
|
64
|
+
worse than a clean core-block composition. Use judgment: does the matched asset actually serve
|
|
65
|
+
what the user asked for, or just share a keyword?
|
|
66
|
+
|
|
67
|
+
Upstream concepts: synced patterns
|
|
68
|
+
<https://wordpress.org/documentation/article/reusable-blocks/>,
|
|
69
|
+
theme patterns <https://developer.wordpress.org/themes/patterns/>,
|
|
70
|
+
template parts <https://developer.wordpress.org/themes/block-themes/template-parts/>.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Recipe: FAQ section (fallback)
|
|
2
|
+
|
|
3
|
+
Use after `search_patterns("faq questions accordion")` finds no good match.
|
|
4
|
+
|
|
5
|
+
## Preferred — Details block (native accordion, no plugin)
|
|
6
|
+
|
|
7
|
+
Core `wp:details` renders an expand/collapse `<details>` natively. Each Q is its own block.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- wp:group {"layout":{"type":"constrained"}} -->
|
|
11
|
+
<div class="wp-block-group">
|
|
12
|
+
<!-- wp:heading -->
|
|
13
|
+
<h2 class="wp-block-heading">Frequently asked questions</h2>
|
|
14
|
+
<!-- /wp:heading -->
|
|
15
|
+
|
|
16
|
+
<!-- wp:details -->
|
|
17
|
+
<details class="wp-block-details"><summary>How does billing work?</summary>
|
|
18
|
+
<!-- wp:paragraph -->
|
|
19
|
+
<p>You're billed monthly and can cancel anytime.</p>
|
|
20
|
+
<!-- /wp:paragraph -->
|
|
21
|
+
</details>
|
|
22
|
+
<!-- /wp:details -->
|
|
23
|
+
|
|
24
|
+
<!-- wp:details -->
|
|
25
|
+
<details class="wp-block-details"><summary>Do you offer support?</summary>
|
|
26
|
+
<!-- wp:paragraph -->
|
|
27
|
+
<p>Yes — email support on every plan.</p>
|
|
28
|
+
<!-- /wp:paragraph -->
|
|
29
|
+
</details>
|
|
30
|
+
<!-- /wp:details -->
|
|
31
|
+
</div>
|
|
32
|
+
<!-- /wp:group -->
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Set `{"showContent":true}` on a `wp:details` to render it open by default.
|
|
36
|
+
|
|
37
|
+
## Simpler — heading + paragraph pairs (no interactivity)
|
|
38
|
+
|
|
39
|
+
If `wp:details` isn't registered on the site (`list_registered_blocks`), or the user wants a
|
|
40
|
+
flat list, alternate headings and paragraphs:
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<!-- wp:heading {"level":3} -->
|
|
44
|
+
<h3 class="wp-block-heading">How does billing work?</h3>
|
|
45
|
+
<!-- /wp:heading -->
|
|
46
|
+
|
|
47
|
+
<!-- wp:paragraph -->
|
|
48
|
+
<p>You're billed monthly and can cancel anytime.</p>
|
|
49
|
+
<!-- /wp:paragraph -->
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Adapt
|
|
53
|
+
|
|
54
|
+
- Generate one `wp:details` (or heading/paragraph pair) per Q&A from the user's content.
|
|
55
|
+
- Don't reach for a third-party accordion block unless the user names one and
|
|
56
|
+
`list_registered_blocks` confirms it's installed — `wp:details` covers most needs.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Recipe: hero / header section (fallback)
|
|
2
|
+
|
|
3
|
+
Use after `search_patterns("hero banner header")` finds no good match. Two variants.
|
|
4
|
+
|
|
5
|
+
## Variant A — Cover (background image + overlay)
|
|
6
|
+
|
|
7
|
+
Best when the user wants an image or color behind the headline. `dimRatio` is overlay
|
|
8
|
+
strength (0–100). Inner blocks: heading, paragraph, buttons.
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<!-- wp:cover {"url":"https://example.com/hero.jpg","dimRatio":50,"minHeight":520,"align":"full"} -->
|
|
12
|
+
<div class="wp-block-cover alignfull" style="min-height:520px"><span aria-hidden="true" class="wp-block-cover__background has-background-dim"></span><img class="wp-block-cover__image-background" src="https://example.com/hero.jpg" alt=""/><div class="wp-block-cover__inner-container">
|
|
13
|
+
<!-- wp:heading {"textAlign":"center","level":1,"fontSize":"xx-large"} -->
|
|
14
|
+
<h1 class="wp-block-heading has-text-align-center has-xx-large-font-size">Build faster with Linchpin</h1>
|
|
15
|
+
<!-- /wp:heading -->
|
|
16
|
+
|
|
17
|
+
<!-- wp:paragraph {"align":"center"} -->
|
|
18
|
+
<p class="has-text-align-center">A short, punchy subhead that explains the value in one line.</p>
|
|
19
|
+
<!-- /wp:paragraph -->
|
|
20
|
+
|
|
21
|
+
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
|
|
22
|
+
<div class="wp-block-buttons"><!-- wp:button -->
|
|
23
|
+
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="/contact">Get started</a></div>
|
|
24
|
+
<!-- /wp:button --></div>
|
|
25
|
+
<!-- /wp:buttons -->
|
|
26
|
+
</div></div>
|
|
27
|
+
<!-- /wp:cover -->
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Variant B — Group + columns (text left, image right, no overlay)
|
|
31
|
+
|
|
32
|
+
Best for a clean split layout.
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<!-- wp:group {"align":"wide","layout":{"type":"constrained"}} -->
|
|
36
|
+
<div class="wp-block-group alignwide">
|
|
37
|
+
<!-- wp:columns {"verticalAlignment":"center"} -->
|
|
38
|
+
<div class="wp-block-columns are-vertically-aligned-center"><!-- wp:column {"verticalAlignment":"center"} -->
|
|
39
|
+
<div class="wp-block-column is-vertically-aligned-center">
|
|
40
|
+
<!-- wp:heading {"level":1,"fontSize":"xx-large"} -->
|
|
41
|
+
<h1 class="wp-block-heading has-xx-large-font-size">A headline that sells</h1>
|
|
42
|
+
<!-- /wp:heading -->
|
|
43
|
+
|
|
44
|
+
<!-- wp:paragraph -->
|
|
45
|
+
<p>One or two sentences of supporting copy.</p>
|
|
46
|
+
<!-- /wp:paragraph -->
|
|
47
|
+
|
|
48
|
+
<!-- wp:buttons -->
|
|
49
|
+
<div class="wp-block-buttons"><!-- wp:button -->
|
|
50
|
+
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="/contact">Get started</a></div>
|
|
51
|
+
<!-- /wp:button --></div>
|
|
52
|
+
<!-- /wp:buttons -->
|
|
53
|
+
</div>
|
|
54
|
+
<!-- /wp:column -->
|
|
55
|
+
|
|
56
|
+
<!-- wp:column {"verticalAlignment":"center"} -->
|
|
57
|
+
<div class="wp-block-column is-vertically-aligned-center">
|
|
58
|
+
<!-- wp:image {"sizeSlug":"large"} -->
|
|
59
|
+
<figure class="wp-block-image size-large"><img src="https://example.com/hero.jpg" alt=""/></figure>
|
|
60
|
+
<!-- /wp:image -->
|
|
61
|
+
</div>
|
|
62
|
+
<!-- /wp:column --></div>
|
|
63
|
+
<!-- /wp:columns -->
|
|
64
|
+
</div>
|
|
65
|
+
<!-- /wp:group -->
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Adapt
|
|
69
|
+
|
|
70
|
+
- Use `level:1` for the headline only if this is the page's main hero; otherwise `level:2`.
|
|
71
|
+
- Swap colors/sizes for `get_theme_tokens()` slugs. For a solid-color background instead of an
|
|
72
|
+
image, drop the `url`/`img` and add `{"customOverlayColor":"…"}` or a `backgroundColor` token.
|
|
73
|
+
- `align:"full"` / `align:"wide"` only render edge-to-edge if the theme supports those widths
|
|
74
|
+
(`get_theme_tokens().layout`).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Recipe: pricing table (fallback)
|
|
2
|
+
|
|
3
|
+
Use this **only after** `search_patterns("pricing plans tiers")` finds no good synced/registered
|
|
4
|
+
pattern. This is a three-tier table composed from core blocks. Pull color/font slugs from
|
|
5
|
+
`get_theme_tokens()` and swap them in; `validate_blocks` the result.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
A constrained `group` (section heading) followed by a `columns` block — one `column` per plan,
|
|
10
|
+
each a bordered `group` holding: plan name (`heading`), price (`heading`), feature `list`, and a
|
|
11
|
+
`buttons` CTA.
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!-- wp:group {"layout":{"type":"constrained"}} -->
|
|
15
|
+
<div class="wp-block-group">
|
|
16
|
+
<!-- wp:heading {"textAlign":"center"} -->
|
|
17
|
+
<h2 class="wp-block-heading has-text-align-center">Simple, transparent pricing</h2>
|
|
18
|
+
<!-- /wp:heading -->
|
|
19
|
+
|
|
20
|
+
<!-- wp:columns {"align":"wide"} -->
|
|
21
|
+
<div class="wp-block-columns alignwide"><!-- wp:column -->
|
|
22
|
+
<div class="wp-block-column">
|
|
23
|
+
<!-- wp:group {"style":{"border":{"width":"1px","radius":"8px"},"spacing":{"padding":{"top":"2rem","right":"2rem","bottom":"2rem","left":"2rem"}}},"layout":{"type":"constrained"}} -->
|
|
24
|
+
<div class="wp-block-group" style="border-width:1px;border-radius:8px;padding-top:2rem;padding-right:2rem;padding-bottom:2rem;padding-left:2rem">
|
|
25
|
+
<!-- wp:heading {"level":3,"textAlign":"center"} -->
|
|
26
|
+
<h3 class="wp-block-heading has-text-align-center">Starter</h3>
|
|
27
|
+
<!-- /wp:heading -->
|
|
28
|
+
|
|
29
|
+
<!-- wp:heading {"textAlign":"center","fontSize":"x-large"} -->
|
|
30
|
+
<h2 class="wp-block-heading has-text-align-center has-x-large-font-size">$19<span>/mo</span></h2>
|
|
31
|
+
<!-- /wp:heading -->
|
|
32
|
+
|
|
33
|
+
<!-- wp:list -->
|
|
34
|
+
<ul class="wp-block-list"><!-- wp:list-item -->
|
|
35
|
+
<li>1 site</li>
|
|
36
|
+
<!-- /wp:list-item -->
|
|
37
|
+
|
|
38
|
+
<!-- wp:list-item -->
|
|
39
|
+
<li>Community support</li>
|
|
40
|
+
<!-- /wp:list-item --></ul>
|
|
41
|
+
<!-- /wp:list -->
|
|
42
|
+
|
|
43
|
+
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
|
|
44
|
+
<div class="wp-block-buttons"><!-- wp:button -->
|
|
45
|
+
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="#">Choose Starter</a></div>
|
|
46
|
+
<!-- /wp:button --></div>
|
|
47
|
+
<!-- /wp:buttons -->
|
|
48
|
+
</div>
|
|
49
|
+
<!-- /wp:group -->
|
|
50
|
+
</div>
|
|
51
|
+
<!-- /wp:column -->
|
|
52
|
+
|
|
53
|
+
<!-- wp:column -->
|
|
54
|
+
<div class="wp-block-column">
|
|
55
|
+
<!-- duplicate the bordered group; vary name/price/features (e.g. "Pro", "$49/mo") -->
|
|
56
|
+
</div>
|
|
57
|
+
<!-- /wp:column -->
|
|
58
|
+
|
|
59
|
+
<!-- wp:column -->
|
|
60
|
+
<div class="wp-block-column">
|
|
61
|
+
<!-- third plan (e.g. "Business", "$99/mo") -->
|
|
62
|
+
</div>
|
|
63
|
+
<!-- /wp:column --></div>
|
|
64
|
+
<!-- /wp:columns -->
|
|
65
|
+
</div>
|
|
66
|
+
<!-- /wp:group -->
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Adapt
|
|
70
|
+
|
|
71
|
+
- Fill real plan names, prices, and features from the user's request.
|
|
72
|
+
- Highlight a "most popular" tier with a token background: add
|
|
73
|
+
`{"backgroundColor":"primary","textColor":"white"}` to that column's inner `group` (and the
|
|
74
|
+
matching `has-…` classes — let `validate_blocks` reconcile them).
|
|
75
|
+
- 2 plans → two columns; 4 → consider two rows of `columns` or narrower columns.
|
|
76
|
+
- Replace inline border/padding `style` with theme spacing/border tokens when
|
|
77
|
+
`get_theme_tokens()` exposes them.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Tool contract — the live site interface
|
|
2
|
+
|
|
3
|
+
The static knowledge in this skill is generic. The **dynamic** half — *this* site's synced
|
|
4
|
+
patterns, registered patterns, template parts, registered blocks, and theme tokens — lives in
|
|
5
|
+
WordPress and changes per site and over time. It can't be a markdown file; the host (the Mantle
|
|
6
|
+
plugin) must expose it as **tools** the model calls at request time.
|
|
7
|
+
|
|
8
|
+
Design goals:
|
|
9
|
+
- **Index, then fetch.** `search_patterns` returns lightweight rows (title/slug/categories/
|
|
10
|
+
excerpt) so the model can choose cheaply; `get_pattern` pulls full markup only for the one
|
|
11
|
+
it picks. Never dump every pattern's full markup into the prompt.
|
|
12
|
+
- **Function-calling friendly.** Schemas below are JSON Schema, usable directly as OpenAI
|
|
13
|
+
`tools[].function.parameters`.
|
|
14
|
+
|
|
15
|
+
Implementation notes (host side): synced patterns come from the `wp_block` CPT; registered
|
|
16
|
+
patterns from `WP_Block_Patterns_Registry`; registered blocks from `WP_Block_Type_Registry`;
|
|
17
|
+
template parts from the `wp_template_part` source; tokens from the active theme's `theme.json`
|
|
18
|
+
(`wp_get_global_settings()`).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## `search_patterns`
|
|
23
|
+
|
|
24
|
+
> Search the site's reusable assets (synced + registered patterns and template parts). Call
|
|
25
|
+
> this FIRST for any layout/section request, before composing core blocks.
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"name": "search_patterns",
|
|
30
|
+
"description": "Search the site's synced patterns, registered patterns, and template parts by keyword. Returns lightweight matches; call get_pattern to retrieve full markup.",
|
|
31
|
+
"parameters": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"query": { "type": "string", "description": "Keywords from the user's intent, e.g. 'pricing plans tiers'." },
|
|
35
|
+
"types": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": { "type": "string", "enum": ["synced", "registered", "template_part"] },
|
|
38
|
+
"description": "Optional filter. Default: all three."
|
|
39
|
+
},
|
|
40
|
+
"limit": { "type": "integer", "minimum": 1, "maximum": 25, "default": 8 }
|
|
41
|
+
},
|
|
42
|
+
"required": ["query"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Returns:** `{ "matches": Match[] }`
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"matches": [
|
|
51
|
+
{
|
|
52
|
+
"title": "Pricing — 3 column",
|
|
53
|
+
"type": "synced",
|
|
54
|
+
"ref": 451,
|
|
55
|
+
"slug": null,
|
|
56
|
+
"categories": ["pricing", "featured"],
|
|
57
|
+
"excerpt": "Three plan cards with monthly price and CTA.",
|
|
58
|
+
"score": 0.92
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"title": "Pricing table (registered)",
|
|
62
|
+
"type": "registered",
|
|
63
|
+
"ref": null,
|
|
64
|
+
"slug": "linchpin/pricing-3col",
|
|
65
|
+
"categories": ["pricing"],
|
|
66
|
+
"excerpt": "Theme-provided 3-tier layout.",
|
|
67
|
+
"score": 0.78
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
- `ref` is set for `synced` (the `wp_block` post ID → emit `wp:block {"ref":…}`).
|
|
73
|
+
- `slug` is set for `registered` and `template_part`.
|
|
74
|
+
|
|
75
|
+
## `get_pattern`
|
|
76
|
+
|
|
77
|
+
> Fetch the full block markup for one match. For synced patterns prefer emitting the
|
|
78
|
+
> reference block; only fetch expanded markup when detaching/editing a single instance.
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"name": "get_pattern",
|
|
83
|
+
"description": "Return the full block markup for one pattern or template part, by ref or slug.",
|
|
84
|
+
"parameters": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"ref": { "type": "integer", "description": "wp_block post ID for a synced pattern." },
|
|
88
|
+
"slug": { "type": "string", "description": "Slug for a registered pattern or template part." }
|
|
89
|
+
},
|
|
90
|
+
"oneOf": [{ "required": ["ref"] }, { "required": ["slug"] }]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
**Returns:** `{ "title": string, "type": "synced"|"registered"|"template_part", "block_markup": string, "ref": int|null, "slug": string|null }`
|
|
95
|
+
|
|
96
|
+
## `list_template_parts`
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"name": "list_template_parts",
|
|
101
|
+
"description": "List the active block theme's template parts (FSE). For template editing, not post body.",
|
|
102
|
+
"parameters": { "type": "object", "properties": {} }
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
**Returns:** `{ "parts": [{ "slug": string, "title": string, "area": "header"|"footer"|"uncategorized"|string, "theme": string }] }`
|
|
106
|
+
|
|
107
|
+
## `list_registered_blocks`
|
|
108
|
+
|
|
109
|
+
> Confirm a non-core block actually exists on this install before referencing it.
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"name": "list_registered_blocks",
|
|
114
|
+
"description": "List blocks registered on this site. Filter by namespace to check availability of a specific block.",
|
|
115
|
+
"parameters": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"properties": {
|
|
118
|
+
"namespace": { "type": "string", "description": "e.g. 'linchpin' or 'core'. Omit for all." }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
**Returns:** `{ "blocks": [{ "name": string, "title": string, "category": string, "supports_inner_blocks": boolean }] }`
|
|
124
|
+
|
|
125
|
+
## `get_theme_tokens`
|
|
126
|
+
|
|
127
|
+
> Pull theme.json design tokens so generated markup matches the site's design system.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"name": "get_theme_tokens",
|
|
132
|
+
"description": "Return the active theme's design tokens (colors, gradients, font sizes, spacing, layout widths) from theme.json.",
|
|
133
|
+
"parameters": { "type": "object", "properties": {} }
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
**Returns:**
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"colors": [{ "slug": "primary", "name": "Primary", "color": "#0b5fff" }],
|
|
140
|
+
"gradients": [{ "slug": "brand", "name": "Brand", "gradient": "linear-gradient(...)" }],
|
|
141
|
+
"font_sizes": [{ "slug": "large", "name": "Large", "size": "1.5rem" }],
|
|
142
|
+
"spacing": { "spacingSizes": [{ "slug": "40", "name": "Large", "size": "2rem" }] },
|
|
143
|
+
"layout": { "contentSize": "840px", "wideSize": "1200px" }
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## `validate_blocks` (required before insert)
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"name": "validate_blocks",
|
|
152
|
+
"description": "Parse block markup the way the editor does and report validity. Optionally return a normalized/repaired version.",
|
|
153
|
+
"parameters": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"block_markup": { "type": "string" },
|
|
157
|
+
"autofix": { "type": "boolean", "default": true, "description": "Return a normalized version with corrected wrappers/classes where possible." }
|
|
158
|
+
},
|
|
159
|
+
"required": ["block_markup"]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
**Returns:** `{ "valid": boolean, "issues": [{ "block": string, "message": string }], "fixed_markup": string|null }`
|
|
164
|
+
|
|
165
|
+
> The host already has block validators available (e.g. the WordPress Studio MCP
|
|
166
|
+
> `validate_and_fix_blocks` / `validate_html_blocks` tools) — wire `validate_blocks` to one of
|
|
167
|
+
> those rather than reimplementing the parser.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Validation — always, before insert
|
|
2
|
+
|
|
3
|
+
Block markup that *looks* right can still be rejected by the editor (a drifted wrapper class,
|
|
4
|
+
a void/open mismatch, attribute JSON that doesn't match the saved HTML). A rejected block
|
|
5
|
+
shows as *"This block contains unexpected or invalid content"* and the user loses the layout.
|
|
6
|
+
So validation is not optional — it's the last step of every content generation.
|
|
7
|
+
|
|
8
|
+
## The contract
|
|
9
|
+
|
|
10
|
+
1. Generate the block markup (reused asset or composed core blocks).
|
|
11
|
+
2. Call `validate_blocks({ block_markup, autofix: true })` (see `tool-contract.md`).
|
|
12
|
+
3. Branch on the result:
|
|
13
|
+
- `valid: true` → insert the markup (use `fixed_markup` if present — it's normalized).
|
|
14
|
+
- `valid: false` → read `issues`, repair, and re-validate. Common repairs:
|
|
15
|
+
- void/open mismatch (`/-->` vs `-->`) — see `block-grammar.md`
|
|
16
|
+
- missing/wrong `wp-block-*` wrapper class or tag
|
|
17
|
+
- attribute JSON not matching generated classes (e.g. `textColor` set but `has-…-color`
|
|
18
|
+
class missing)
|
|
19
|
+
- a referenced block that isn't registered (`list_registered_blocks` to confirm) — drop
|
|
20
|
+
to a core-block equivalent
|
|
21
|
+
- Still invalid after a repair pass → **simplify**. Fall back to a plainer composition
|
|
22
|
+
(e.g. paragraphs + a single columns block) rather than shipping invalid markup. A simpler
|
|
23
|
+
layout that renders beats a fancy one that breaks.
|
|
24
|
+
4. Never return un-validated markup to the editor, and never paper over failure with prose.
|
|
25
|
+
|
|
26
|
+
## Why repair instead of guess
|
|
27
|
+
|
|
28
|
+
Saved-HTML shapes drift across WordPress/Gutenberg versions, so the exact classes in this
|
|
29
|
+
skill's examples won't be byte-perfect on every site. The validator reflects *this* site's
|
|
30
|
+
actual block versions — trust it over hand-written markup. Prefer `fixed_markup` from the
|
|
31
|
+
validator when it's offered; it's the site's own normalization of your intent.
|
|
32
|
+
|
|
33
|
+
## Host wiring
|
|
34
|
+
|
|
35
|
+
Point `validate_blocks` at a real WordPress parser so "valid" means what the editor means.
|
|
36
|
+
The WordPress Studio MCP server exposes `validate_html_blocks` and `validate_and_fix_blocks`
|
|
37
|
+
for exactly this; reuse one instead of writing a parser. The `autofix`/`fixed_markup` path
|
|
38
|
+
maps to `validate_and_fix_blocks`.
|