@silverassist/agents-toolkit 2.3.0 → 2.5.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/LICENSE +2 -2
- package/README.md +166 -33
- package/bin/cli.js +511 -16
- package/package.json +1 -1
- package/src/index.js +49 -9
- package/templates/agents/AGENTS.md +33 -5
- package/templates/shared/hooks/lint-format.json +1 -0
- package/templates/shared/hooks/validate-tsx.json +1 -0
- package/templates/shared/instructions/caching.instructions.md +121 -0
- package/templates/shared/instructions/seo-ai-optimization.instructions.md +272 -0
- package/templates/shared/prompts/_partials/release-node.md +58 -0
- package/templates/shared/prompts/_partials/release-wordpress.md +56 -0
- package/templates/shared/prompts/_partials/validations.md +1 -0
- package/templates/shared/prompts/create-github-pr.prompt.md +147 -0
- package/templates/shared/prompts/create-plan.prompt.md +8 -0
- package/templates/shared/prompts/finalize-github-pr.prompt.md +129 -0
- package/templates/shared/prompts/prepare-github-release.prompt.md +134 -0
- package/templates/shared/prompts/review-code.prompt.md +12 -0
- package/templates/shared/prompts/work-github-issue.prompt.md +1 -1
- package/templates/shared/skills/README.md +25 -3
- package/templates/shared/skills/nextjs-caching/SKILL.md +184 -0
- package/templates/shared/prompts/prepare-release.prompt.md +0 -74
package/src/index.js
CHANGED
|
@@ -3,45 +3,84 @@
|
|
|
3
3
|
* @module @silverassist/agents-toolkit
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export const VERSION = "2.
|
|
6
|
+
export const VERSION = "2.5.0";
|
|
7
7
|
|
|
8
8
|
export const PROMPTS = {
|
|
9
9
|
workflow: [
|
|
10
|
+
"analyze-github-issue",
|
|
10
11
|
"analyze-ticket",
|
|
12
|
+
"create-github-pr",
|
|
11
13
|
"create-plan",
|
|
12
|
-
"work-ticket",
|
|
13
|
-
"prepare-pr",
|
|
14
14
|
"create-pr",
|
|
15
|
+
"finalize-github-pr",
|
|
15
16
|
"finalize-pr",
|
|
17
|
+
"prepare-github-release",
|
|
18
|
+
"prepare-pr",
|
|
19
|
+
"work-github-issue",
|
|
20
|
+
"work-ticket",
|
|
21
|
+
],
|
|
22
|
+
utility: [
|
|
23
|
+
"add-tests",
|
|
24
|
+
"audit-ai-seo",
|
|
25
|
+
"fix-issues",
|
|
26
|
+
"new-wp-component",
|
|
27
|
+
"new-wp-plugin",
|
|
28
|
+
"quality-check",
|
|
29
|
+
"review-code",
|
|
16
30
|
],
|
|
17
|
-
utility: ["review-code", "fix-issues", "add-tests"],
|
|
18
31
|
};
|
|
19
32
|
|
|
20
33
|
export const PARTIALS = [
|
|
21
|
-
"
|
|
34
|
+
"documentation",
|
|
22
35
|
"git-operations",
|
|
36
|
+
"github-integration",
|
|
23
37
|
"jira-integration",
|
|
24
|
-
"documentation",
|
|
25
38
|
"pr-template",
|
|
39
|
+
"release-node",
|
|
40
|
+
"release-wordpress",
|
|
41
|
+
"validations",
|
|
26
42
|
];
|
|
27
43
|
|
|
28
44
|
export const INSTRUCTIONS = [
|
|
29
|
-
"
|
|
45
|
+
"caching",
|
|
46
|
+
"css-styling",
|
|
47
|
+
"documentation-language",
|
|
48
|
+
"github-workflow",
|
|
49
|
+
"php-standards",
|
|
30
50
|
"react-components",
|
|
51
|
+
"seo-ai-optimization",
|
|
31
52
|
"server-actions",
|
|
53
|
+
"testing-standards",
|
|
32
54
|
"tests",
|
|
33
|
-
"
|
|
55
|
+
"typescript",
|
|
56
|
+
"wordpress-plugin-architecture",
|
|
34
57
|
];
|
|
35
58
|
|
|
36
59
|
export const SKILLS = [
|
|
60
|
+
"ai-seo-optimization",
|
|
37
61
|
"component-architecture",
|
|
62
|
+
"create-component",
|
|
38
63
|
"domain-driven-design",
|
|
64
|
+
"nextjs-caching",
|
|
65
|
+
"plugin-creation",
|
|
66
|
+
"quality-checks",
|
|
67
|
+
"release-management",
|
|
68
|
+
"testing",
|
|
39
69
|
"testing-patterns",
|
|
40
|
-
"ai-seo-optimization",
|
|
41
70
|
];
|
|
42
71
|
|
|
43
72
|
export const HOOKS = ["validate-tsx", "lint-format"];
|
|
44
73
|
|
|
74
|
+
// Skills follow the `npx skills` standard: a single canonical copy lives in
|
|
75
|
+
// .agents/skills/ and each agent's skills directory symlinks to it.
|
|
76
|
+
export const SKILLS_LAYOUT = {
|
|
77
|
+
canonicalDir: ".agents/skills",
|
|
78
|
+
agentDirs: {
|
|
79
|
+
claude: ".claude/skills",
|
|
80
|
+
copilot: ".github/skills",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
45
84
|
// Claude Code equivalents
|
|
46
85
|
export const CLAUDE_COMMANDS = [
|
|
47
86
|
"analyze-ticket",
|
|
@@ -58,4 +97,5 @@ export const CLAUDE_COMMANDS = [
|
|
|
58
97
|
export const CLAUDE_FILES = {
|
|
59
98
|
instructions: "CLAUDE.md",
|
|
60
99
|
commandsDir: ".claude/commands",
|
|
100
|
+
skillsDir: ".claude/skills",
|
|
61
101
|
};
|
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
[Instructions]|root:.github/instructions
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
16
|
+
|caching.instructions.md → Next.js caching: read-vs-mutation fetch, ISR tiers, CDN invalidation
|
|
17
|
+
|css-styling.instructions.md → CSS/Tailwind patterns, cn() utility, responsive design
|
|
18
|
+
|react-components.instructions.md → Component structure, exports, props, early returns
|
|
19
|
+
|seo-ai-optimization.instructions.md → Semantic HTML, a11y tree, metadata, JSON-LD for AI Search
|
|
20
|
+
|server-actions.instructions.md → Server action patterns, validation, error handling
|
|
21
|
+
|tests.instructions.md → Test structure, mocking, assertions
|
|
22
|
+
|typescript.instructions.md → Type safety, destructuring, JSDoc
|
|
21
23
|
|
|
22
24
|
[Prompts]|root:.github/prompts
|
|
23
25
|
|add-tests,analyze-ticket,create-plan,create-pr,finalize-pr,fix-issues,prepare-pr,review-code,work-ticket
|
|
@@ -133,6 +135,30 @@ import { myFunction } from '@/lib/my-module';
|
|
|
133
135
|
|
|
134
136
|
---
|
|
135
137
|
|
|
138
|
+
## 🗄️ Caching Rules (CRITICAL)
|
|
139
|
+
|
|
140
|
+
| Rule | Requirement |
|
|
141
|
+
|------|-------------|
|
|
142
|
+
| **Cache by intent** | Cache reads, never mutations — decide on intent, NOT the HTTP method |
|
|
143
|
+
| **POST reads** | A `POST` read (e.g. filter/`geo-search`) MUST set `next: { revalidate, tags }` to cache |
|
|
144
|
+
| **Mutations** | `submit`/lead/`PUT`/`DELETE` → `cache: "no-store"` (flag with `mutation: true`) |
|
|
145
|
+
| **ISR** | Every cacheable route exports `revalidate`; never on form/personalized routes |
|
|
146
|
+
| **Invalidation** | `/api/revalidate` calls `revalidateTag`/`revalidatePath` AND the CDN invalidation |
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// ❌ WRONG: leaves POST reads uncached → route turns dynamic (private, no-store)
|
|
150
|
+
next: method === "GET" ? { revalidate, tags } : { revalidate: 0 }
|
|
151
|
+
|
|
152
|
+
// ✅ CORRECT: reads cache regardless of method; mutations opt out
|
|
153
|
+
const isMutation = mutation || method === "PUT" || method === "DELETE";
|
|
154
|
+
...(isMutation ? { cache: "no-store" } : { next: { revalidate, tags } })
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
📄 **Full details:** `.github/instructions/caching.instructions.md`
|
|
158
|
+
📄 **Project-specific context (if present):** `docs/CACHING.md`
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
136
162
|
## 📝 Git Conventions
|
|
137
163
|
|
|
138
164
|
| Type | Format |
|
|
@@ -190,6 +216,8 @@ Before ANY push to dev/staging/main:
|
|
|
190
216
|
| Creating/editing components | `react-components.instructions.md` |
|
|
191
217
|
| Writing CSS/Tailwind | `css-styling.instructions.md` |
|
|
192
218
|
| Creating server actions | `server-actions.instructions.md` |
|
|
219
|
+
| Data fetching / `next.config` / routes / ISR / revalidate | `caching.instructions.md` |
|
|
220
|
+
| Pages, metadata, JSON-LD, accessibility/SEO | `seo-ai-optimization.instructions.md` |
|
|
193
221
|
| Writing tests | `tests.instructions.md` |
|
|
194
222
|
| TypeScript questions | `typescript.instructions.md` |
|
|
195
223
|
| **Before pushing/PR** | `tests.instructions.md` + run quality checks |
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/next.config.*,**/src/proxy.ts,**/src/lib/**/*.ts,**/src/app/**/route.ts,**/src/app/**/page.tsx"
|
|
3
|
+
---
|
|
4
|
+
# Caching Standards (CRITICAL)
|
|
5
|
+
|
|
6
|
+
These rules are **mandatory** when touching data fetching, route segment configs, the asset/page
|
|
7
|
+
proxy, image config, or the on-demand revalidate route. Caching has several **independent layers**
|
|
8
|
+
(ISR page cache, data-fetch cache, request dedup, asset proxy, edge/CDN) — fixing one does not fix
|
|
9
|
+
the others.
|
|
10
|
+
|
|
11
|
+
> Apply to any Silver Side Next.js (App Router) frontend consuming the CCDS API and/or WordPress
|
|
12
|
+
> (headless GraphQL). If the project keeps a `docs/CACHING.md`, treat it as the extended reference.
|
|
13
|
+
|
|
14
|
+
## Hard rules
|
|
15
|
+
|
|
16
|
+
1. **Cache reads, not mutations — decide on intent, NEVER on the HTTP method.**
|
|
17
|
+
Some reads must use `POST` because they take a body (e.g. CCDS `geo-search`). They still have to
|
|
18
|
+
cache like a `GET`. Mutations (`submit-review`, lead submit, and any `PUT`/`DELETE`/`PATCH`) must
|
|
19
|
+
stay uncached with `cache: "no-store"`. Expose a `mutation: true` flag on the client and mark every
|
|
20
|
+
write with it.
|
|
21
|
+
- ❌ **Never** gate caching on `method === "GET"` (e.g. `next: method === "GET" ? {...} : { revalidate: 0 }`).
|
|
22
|
+
That leaves POST reads uncached and forces the whole route into dynamic rendering
|
|
23
|
+
(`cache-control: private, no-store`).
|
|
24
|
+
|
|
25
|
+
2. **A `POST` IS cacheable cross-request — but only with explicit `next` options.**
|
|
26
|
+
Next.js does not cache `POST` by default. Any read `fetch` to CCDS or the WP GraphQL endpoint —
|
|
27
|
+
GET or POST — must set `next: { revalidate, tags }`. The request body is part of the cache key, so
|
|
28
|
+
different filter bodies cache independently.
|
|
29
|
+
|
|
30
|
+
3. **Always pair `tags` with a `revalidate` duration.** `next: { tags }` alone either holds stale data
|
|
31
|
+
indefinitely or (Next 16 default) does not cache at runtime at all. Use a `CACHE_DURATIONS`-style
|
|
32
|
+
default (e.g. 24h) so freshness survives a missed webhook.
|
|
33
|
+
|
|
34
|
+
4. **`React.cache()` is request dedup only.** It collapses duplicate calls within a single render. It
|
|
35
|
+
does **not** cache across requests and is never a substitute for `next: { revalidate }`.
|
|
36
|
+
|
|
37
|
+
5. **Every cacheable route exports `revalidate`.** Suggested tiers: listing 24h–30d, detail/city 24h
|
|
38
|
+
(`86400`), state/landing 7d (`604800`), WP catch-all 7d. Do **not** add `revalidate` to
|
|
39
|
+
mutation/personalized routes (forms, thank-you, wizards). Prefer `export const revalidate` over
|
|
40
|
+
`dynamic = "force-static"` so a failed ISR revalidation preserves the last good cache instead of
|
|
41
|
+
overwriting it with a broken page.
|
|
42
|
+
|
|
43
|
+
6. **On-demand revalidation dual-invalidates.** `/api/revalidate` must call `revalidateTag`/
|
|
44
|
+
`revalidatePath` **and** the CDN invalidation (e.g. `invalidateCloudFrontPaths`) in the same
|
|
45
|
+
request, otherwise the CDN keeps serving stale until its own TTL.
|
|
46
|
+
|
|
47
|
+
7. **Image config** in `next.config`: set `minimumCacheTTL: 2592000` (30d), `qualities`, and
|
|
48
|
+
`formats: ["image/avif", "image/webp"]`. No malformed `remotePatterns` hostnames.
|
|
49
|
+
|
|
50
|
+
8. **Asset proxy TTLs** are type-keyed (image/css/font) at `365d + SWR 30d`.
|
|
51
|
+
|
|
52
|
+
## Canonical snippet — API client (read vs. mutation)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
interface FetchOptions {
|
|
56
|
+
endpoint: string;
|
|
57
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
58
|
+
body?: object | null;
|
|
59
|
+
revalidateTag?: string;
|
|
60
|
+
revalidate?: number;
|
|
61
|
+
/** Writes are never cached. Reads (default) cache regardless of method. */
|
|
62
|
+
mutation?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function fetchData<T>({
|
|
66
|
+
endpoint,
|
|
67
|
+
method = "GET",
|
|
68
|
+
body = null,
|
|
69
|
+
revalidateTag,
|
|
70
|
+
revalidate = 86400, // 24h time-based fallback; pair with tags for surgical invalidation
|
|
71
|
+
mutation = false,
|
|
72
|
+
}: FetchOptions): Promise<T | null> {
|
|
73
|
+
const isMutation = mutation || method === "PUT" || method === "DELETE";
|
|
74
|
+
|
|
75
|
+
const requestOptions: RequestInit = {
|
|
76
|
+
method,
|
|
77
|
+
// Reads (GET or POST) cache cross-request via `next`; mutations opt out.
|
|
78
|
+
...(isMutation
|
|
79
|
+
? { cache: "no-store" as RequestCache }
|
|
80
|
+
: { next: { revalidate, tags: revalidateTag ? [revalidateTag] : [] } }),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if ((method === "POST" || method === "PUT") && body) {
|
|
84
|
+
requestOptions.body = JSON.stringify(body);
|
|
85
|
+
}
|
|
86
|
+
// ...fetch + error handling...
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Canonical snippet — WordPress GraphQL client (cached POST)
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
export const WP_CACHE_DURATIONS = {
|
|
94
|
+
pages: 86400, posts: 86400, menus: 86400, staticPages: 604800, default: 86400,
|
|
95
|
+
} as const;
|
|
96
|
+
|
|
97
|
+
export async function fetchWPAPI<T>(
|
|
98
|
+
query: string,
|
|
99
|
+
{ variables, revalidate = WP_CACHE_DURATIONS.default, tags }: {
|
|
100
|
+
variables?: Record<string, unknown>; revalidate?: number; tags?: string[];
|
|
101
|
+
} = {},
|
|
102
|
+
) {
|
|
103
|
+
const res = await fetch(WP_API_URL, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers,
|
|
106
|
+
body: JSON.stringify({ query, variables }),
|
|
107
|
+
next: { revalidate, ...(tags && { tags }) }, // a POST IS cacheable WITH next options
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Do NOT
|
|
113
|
+
|
|
114
|
+
- ❌ `next: method === "GET" ? {...} : { revalidate: 0 }` — leaves POST reads uncached.
|
|
115
|
+
- ❌ `fetch(API_URL, { method: "POST", body })` with no `next` options (bare, uncached POST read).
|
|
116
|
+
- ❌ `next: { tags }` with no `revalidate`.
|
|
117
|
+
- ❌ Adding `export const revalidate` to a form/mutation/personalized route.
|
|
118
|
+
- ❌ Treating `React.cache()` as cross-request caching.
|
|
119
|
+
- ❌ Caching a mutation (`submit-review`, lead submit, `PUT`/`DELETE`/`PATCH`).
|
|
120
|
+
- ❌ Mixing `cacheComponents: true` (`"use cache"`) with route-segment `export const revalidate` —
|
|
121
|
+
that is a separate, deliberate migration; do not introduce it ad hoc.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.tsx"
|
|
3
|
+
---
|
|
4
|
+
# SEO & AI Optimization Patterns
|
|
5
|
+
|
|
6
|
+
These rules ensure components are optimized for Google's generative AI features (AI Overviews, AI
|
|
7
|
+
Mode) and browser-agent interactions: agent-friendly HTML, a clean accessibility tree, semantic
|
|
8
|
+
structure, and E-E-A-T signals. Pairs with the `ai-seo-optimization` skill and the `audit-ai-seo`
|
|
9
|
+
prompt.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Semantic HTML (CRITICAL)
|
|
14
|
+
|
|
15
|
+
### Interactive Elements
|
|
16
|
+
|
|
17
|
+
**NEVER use `<div>` or `<span>` with click handlers. Use proper semantic elements.**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// ✅ CORRECT: Semantic button
|
|
21
|
+
<button onClick={handleClick} type="button">
|
|
22
|
+
Click me
|
|
23
|
+
</button>
|
|
24
|
+
|
|
25
|
+
// ✅ CORRECT: Navigation link
|
|
26
|
+
<Link href="/page">Go to page</Link>
|
|
27
|
+
|
|
28
|
+
// ❌ WRONG: Non-semantic clickable div
|
|
29
|
+
<div onClick={handleClick} className="cursor-pointer">
|
|
30
|
+
Click me
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
// ❌ WRONG: Span with role hack
|
|
34
|
+
<span role="button" onClick={handleClick}>
|
|
35
|
+
Click me
|
|
36
|
+
</span>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Form Inputs
|
|
40
|
+
|
|
41
|
+
**Every input MUST have an associated label. Placeholder is NOT a substitute.**
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// ✅ CORRECT: Label with htmlFor
|
|
45
|
+
<label htmlFor="email">Email address</label>
|
|
46
|
+
<input id="email" name="email" type="email" required aria-required="true" />
|
|
47
|
+
|
|
48
|
+
// ✅ CORRECT: Wrapped label (implicit association)
|
|
49
|
+
<label>
|
|
50
|
+
Email address
|
|
51
|
+
<input name="email" type="email" required />
|
|
52
|
+
</label>
|
|
53
|
+
|
|
54
|
+
// ❌ WRONG: Placeholder only (invisible to accessibility tree)
|
|
55
|
+
<input placeholder="Enter your email" type="email" />
|
|
56
|
+
|
|
57
|
+
// ❌ WRONG: aria-label without visible label (poor for agents)
|
|
58
|
+
<input aria-label="Email" type="email" />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Heading Hierarchy
|
|
62
|
+
|
|
63
|
+
**Headings must follow logical order. Never skip levels.**
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// ✅ CORRECT: Proper hierarchy
|
|
67
|
+
<h1>Page Title</h1>
|
|
68
|
+
<section>
|
|
69
|
+
<h2>Section Title</h2>
|
|
70
|
+
<h3>Subsection</h3>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
// ❌ WRONG: Skipping h2
|
|
74
|
+
<h1>Page Title</h1>
|
|
75
|
+
<h3>Subsection</h3>
|
|
76
|
+
|
|
77
|
+
// ❌ WRONG: Using heading for styling only
|
|
78
|
+
<h4 className="text-sm font-bold">Small bold text</h4> // Use <p> with classes instead
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Lists
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// ✅ CORRECT: Semantic list
|
|
85
|
+
<ul>
|
|
86
|
+
{items.map(item => <li key={item.id}>{item.name}</li>)}
|
|
87
|
+
</ul>
|
|
88
|
+
|
|
89
|
+
// ❌ WRONG: Div-based list
|
|
90
|
+
<div className="flex flex-col gap-2">
|
|
91
|
+
{items.map(item => <div key={item.id}>{item.name}</div>)}
|
|
92
|
+
</div>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Accessibility for AI Agents
|
|
98
|
+
|
|
99
|
+
### Skip-to-Content Link
|
|
100
|
+
|
|
101
|
+
The root layout MUST include a skip-to-content link as the first focusable element:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<body>
|
|
105
|
+
<a
|
|
106
|
+
href="#main-content"
|
|
107
|
+
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-background focus:text-foreground"
|
|
108
|
+
>
|
|
109
|
+
Skip to main content
|
|
110
|
+
</a>
|
|
111
|
+
{/* Header/Navigation */}
|
|
112
|
+
<main id="main-content">
|
|
113
|
+
{children}
|
|
114
|
+
</main>
|
|
115
|
+
</body>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Accessible Names
|
|
119
|
+
|
|
120
|
+
All interactive elements MUST have an accessible name:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// ✅ CORRECT: Button with text content (name from content)
|
|
124
|
+
<button>Submit form</button>
|
|
125
|
+
|
|
126
|
+
// ✅ CORRECT: Icon button with aria-label
|
|
127
|
+
<button aria-label="Close dialog">
|
|
128
|
+
<XIcon className="h-4 w-4" />
|
|
129
|
+
</button>
|
|
130
|
+
|
|
131
|
+
// ❌ WRONG: Icon button without accessible name
|
|
132
|
+
<button>
|
|
133
|
+
<XIcon className="h-4 w-4" />
|
|
134
|
+
</button>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Image Alt Text
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// ✅ CORRECT: Descriptive alt text
|
|
141
|
+
<Image alt="Two-story brick building with a landscaped courtyard entrance" src={src} />
|
|
142
|
+
|
|
143
|
+
// ✅ CORRECT: Decorative image (empty alt)
|
|
144
|
+
<Image alt="" src={decorativeBg} aria-hidden="true" />
|
|
145
|
+
|
|
146
|
+
// ❌ WRONG: Generic alt text
|
|
147
|
+
<Image alt="photo" src={src} />
|
|
148
|
+
<Image alt="image 1" src={src} />
|
|
149
|
+
|
|
150
|
+
// ❌ WRONG: Missing alt
|
|
151
|
+
<Image src={src} />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Metadata & SEO
|
|
157
|
+
|
|
158
|
+
### generateMetadata Pattern
|
|
159
|
+
|
|
160
|
+
Every page with dynamic content MUST implement `generateMetadata`:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
164
|
+
const { slug } = await params;
|
|
165
|
+
const data = await getData(slug);
|
|
166
|
+
if (!data) return {};
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
title: data.title,
|
|
170
|
+
description: data.description,
|
|
171
|
+
openGraph: {
|
|
172
|
+
title: data.title,
|
|
173
|
+
description: data.description,
|
|
174
|
+
images: data.image ? [{ url: data.image }] : undefined,
|
|
175
|
+
},
|
|
176
|
+
alternates: {
|
|
177
|
+
canonical: getCanonicalUrl(slug),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### No Snippet Blocking
|
|
184
|
+
|
|
185
|
+
**NEVER add `nosnippet` to content pages.** This prevents AI feature inclusion:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// ❌ WRONG: Blocks AI features
|
|
189
|
+
export const metadata = {
|
|
190
|
+
robots: { index: true, follow: true, nosnippet: true },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// ✅ CORRECT: Allow snippets (default behavior)
|
|
194
|
+
export const metadata = {
|
|
195
|
+
robots: { index: true, follow: true },
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Structured Data (JSON-LD)
|
|
202
|
+
|
|
203
|
+
### Implementation Pattern
|
|
204
|
+
|
|
205
|
+
Use a dedicated JSON-LD component in layout components:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
export function JsonLd({ data }: { data: Record<string, unknown> }) {
|
|
209
|
+
return (
|
|
210
|
+
<script
|
|
211
|
+
type="application/ld+json"
|
|
212
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Required Schema by Page Type
|
|
219
|
+
|
|
220
|
+
| Page Type | Required Schema |
|
|
221
|
+
|-----------|----------------|
|
|
222
|
+
| Homepage | `Organization`, `WebSite` with `SearchAction` |
|
|
223
|
+
| Local business / Location | `LocalBusiness` with address, geo, rating |
|
|
224
|
+
| Blog Post | `Article` with author (`Person`), dates |
|
|
225
|
+
| Author Page | `Person` with credentials, `knowsAbout` |
|
|
226
|
+
| FAQ Section | `FAQPage` with Q&A pairs |
|
|
227
|
+
| All Pages | `BreadcrumbList` |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Layout Stability (CLS Prevention)
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// ✅ CORRECT: Explicit dimensions prevent layout shift
|
|
235
|
+
<Image src={src} alt="..." width={800} height={600} />
|
|
236
|
+
|
|
237
|
+
// ✅ CORRECT: Aspect ratio container
|
|
238
|
+
<div className="aspect-video relative">
|
|
239
|
+
<Image src={src} alt="..." fill className="object-cover" />
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
// ❌ WRONG: No dimensions (causes layout shift)
|
|
243
|
+
<img src={src} alt="..." />
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Server-Side Rendering
|
|
249
|
+
|
|
250
|
+
**Critical content MUST render on the server.** Client Components should only wrap interactive UI, not content:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
// ✅ CORRECT: Content in Server Component
|
|
254
|
+
export default async function DetailPage({ params }: Props) {
|
|
255
|
+
const item = await getItem(params.slug);
|
|
256
|
+
return (
|
|
257
|
+
<main id="main-content">
|
|
258
|
+
<h1>{item.name}</h1>
|
|
259
|
+
<p>{item.description}</p>
|
|
260
|
+
<ContactForm itemId={item.id} /> {/* Client Component for form only */}
|
|
261
|
+
</main>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ❌ WRONG: Entire page as Client Component
|
|
266
|
+
"use client";
|
|
267
|
+
export default function DetailPage() {
|
|
268
|
+
const [item, setItem] = useState(null);
|
|
269
|
+
useEffect(() => { fetchItem().then(setItem); }, []);
|
|
270
|
+
// Content invisible to crawlers until JS executes
|
|
271
|
+
}
|
|
272
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Release — Node / npm Partial
|
|
2
|
+
|
|
3
|
+
Version-bump and quality-check mechanics for **Node.js / npm packages** (anything with a
|
|
4
|
+
`package.json`). Included by `prepare-github-release` when the project is detected as a Node package.
|
|
5
|
+
|
|
6
|
+
## Detection signal
|
|
7
|
+
|
|
8
|
+
- A `package.json` exists at the repo root **and** there is no WordPress plugin header
|
|
9
|
+
(`*.php` file with a `Version:` plugin header). If both are present, prefer the WordPress partial.
|
|
10
|
+
|
|
11
|
+
## Version source of truth
|
|
12
|
+
|
|
13
|
+
| File | What to update |
|
|
14
|
+
|------|----------------|
|
|
15
|
+
| `package.json` | `"version": "X.Y.Z"` |
|
|
16
|
+
| `package-lock.json` | top-level `version` **and** the root package entry under `packages[""].version` |
|
|
17
|
+
|
|
18
|
+
Prefer the npm tooling so both files stay in sync automatically:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Bumps package.json + package-lock.json and (by default) creates a commit + tag.
|
|
22
|
+
# Use --no-git-tag-version to keep the version change uncommitted so it can go in the release branch/PR.
|
|
23
|
+
npm version X.Y.Z --no-git-tag-version
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If you edit `package.json` by hand instead, re-sync the lockfile without touching dependencies:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --package-lock-only
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quality checks (run BEFORE bumping)
|
|
33
|
+
|
|
34
|
+
Inspect `package.json` `scripts` and run whatever exists — do not assume script names:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm ci # clean install when a lockfile is present (CI parity)
|
|
38
|
+
npm test --if-present
|
|
39
|
+
npm run lint --if-present
|
|
40
|
+
npm run type-check --if-present
|
|
41
|
+
npm run build --if-present
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Do **not** proceed if any check fails.
|
|
45
|
+
|
|
46
|
+
## What the release usually produces
|
|
47
|
+
|
|
48
|
+
- **npm publish** — most Node packages publish to the npm registry. The publish step almost always
|
|
49
|
+
lives in a workflow triggered by a GitHub Release (`on: release: [created|published]`) or a tag
|
|
50
|
+
push — confirm via the workflow-analysis step in the orchestrator before assuming a bare tag is enough.
|
|
51
|
+
- Optional build artifacts (bundled `dist/`, types) attached to the Release.
|
|
52
|
+
|
|
53
|
+
## Notes
|
|
54
|
+
|
|
55
|
+
- `private: true` in `package.json` means the package is **not** published to npm — the release is
|
|
56
|
+
tag/GitHub-Release only. Surface this to the user.
|
|
57
|
+
- The npm version in `package.json` is the source of truth that `publish.yml`-style workflows read,
|
|
58
|
+
so it must be committed before the tag/Release is created.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Release — WordPress Plugin Partial
|
|
2
|
+
|
|
3
|
+
Version-bump and quality-check mechanics for **Silver Assist WordPress plugins**. Included by
|
|
4
|
+
`prepare-github-release` when the project is detected as a WordPress plugin. See the
|
|
5
|
+
`release-management` skill for full documentation and troubleshooting.
|
|
6
|
+
|
|
7
|
+
## Detection signal
|
|
8
|
+
|
|
9
|
+
- A `*.php` file with a plugin header (`* Version: X.Y.Z`) at the repo root, usually alongside a
|
|
10
|
+
`composer.json` and often a `readme.txt`.
|
|
11
|
+
|
|
12
|
+
## Version source of truth
|
|
13
|
+
|
|
14
|
+
| File | What to update |
|
|
15
|
+
|------|----------------|
|
|
16
|
+
| Main plugin file | `* Version: X.Y.Z` header **and** the `VERSION` constant if defined |
|
|
17
|
+
| `readme.txt` | `Stable tag: X.Y.Z` (if present) |
|
|
18
|
+
| `composer.json` | `"version": "X.Y.Z"` (if present) |
|
|
19
|
+
|
|
20
|
+
Prefer the bundled script so every file stays in sync:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
./scripts/update-version.sh X.Y.Z
|
|
24
|
+
# Some plugins ship the simple variant instead:
|
|
25
|
+
./scripts/update-version-simple.sh X.Y.Z
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quality checks (run BEFORE bumping)
|
|
29
|
+
|
|
30
|
+
Do **not** proceed if any check fails:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
./scripts/run-quality-checks.sh --skip-wp-setup phpcs phpstan
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If the script is absent, fall back to the tools directly:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
composer run phpcs --if-present
|
|
40
|
+
composer run phpstan --if-present
|
|
41
|
+
composer run test --if-present
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## What the release usually produces
|
|
45
|
+
|
|
46
|
+
- **Distributable ZIP** — the GitHub Actions release workflow builds the plugin ZIP and attaches it
|
|
47
|
+
to the GitHub Release. This almost always triggers on `on: release: [created|published]`, so a bare
|
|
48
|
+
tag is typically **not** enough — confirm via the workflow-analysis step in the orchestrator.
|
|
49
|
+
- No npm publish; WordPress plugins are distributed as ZIP artifacts (and/or wordpress.org SVN).
|
|
50
|
+
|
|
51
|
+
## Notes
|
|
52
|
+
|
|
53
|
+
- Some plugins use `master` instead of `main` as the default branch (e.g.
|
|
54
|
+
`silver-assist-post-revalidate`) — always verify with `gh repo view --json defaultBranchRef`.
|
|
55
|
+
- The plugin-header version is what WordPress and the release workflow read, so it must be committed
|
|
56
|
+
before the tag/Release is created.
|
|
@@ -85,3 +85,4 @@ npm run build --if-present
|
|
|
85
85
|
- [ ] No sensitive data exposed (API keys, secrets)
|
|
86
86
|
- [ ] No `any` types introduced
|
|
87
87
|
- [ ] JSDoc comments on new functions
|
|
88
|
+
- [ ] If data fetching / routes / `next.config` changed: caching follows `caching.instructions.md` (reads cache regardless of method, mutations `no-store`, cacheable routes export `revalidate`)
|