@kennethsolomon/shipkit 3.1.0 → 3.3.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.
@@ -0,0 +1,287 @@
1
+ # React + Vite + Tailwind — Stack Reference
2
+
3
+ ## Scaffold
4
+
5
+ ```bash
6
+ npm create vite@latest {project-name} -- --template react-ts
7
+ cd {project-name}
8
+ npm install
9
+ npm install -D tailwindcss @tailwindcss/vite
10
+ npm install react-router-dom
11
+ ```
12
+
13
+ ## Directory Structure
14
+
15
+ ```
16
+ {project-name}/
17
+ ├── src/
18
+ │ ├── main.tsx ← entry point (router setup)
19
+ │ ├── App.tsx ← route definitions
20
+ │ ├── index.css ← Tailwind directives + custom CSS
21
+ │ ├── pages/
22
+ │ │ ├── Landing.tsx ← landing page
23
+ │ │ ├── Dashboard.tsx ← dashboard
24
+ │ │ ├── {Feature1}.tsx
25
+ │ │ ├── {Feature2}.tsx
26
+ │ │ └── Settings.tsx
27
+ │ ├── components/
28
+ │ │ ├── landing/
29
+ │ │ │ ├── Navbar.tsx
30
+ │ │ │ ├── Hero.tsx
31
+ │ │ │ ├── Features.tsx
32
+ │ │ │ ├── HowItWorks.tsx
33
+ │ │ │ ├── Pricing.tsx
34
+ │ │ │ ├── Testimonials.tsx
35
+ │ │ │ ├── WaitlistForm.tsx
36
+ │ │ │ └── Footer.tsx
37
+ │ │ ├── app/
38
+ │ │ │ ├── Sidebar.tsx
39
+ │ │ │ ├── AppLayout.tsx
40
+ │ │ │ ├── DashboardCards.tsx
41
+ │ │ │ └── {feature components}
42
+ │ │ └── ui/
43
+ │ │ ├── Button.tsx
44
+ │ │ ├── Input.tsx
45
+ │ │ ├── Card.tsx
46
+ │ │ ├── Modal.tsx
47
+ │ │ └── Toast.tsx
48
+ │ ├── data/
49
+ │ │ └── mock.ts ← all fake data centralized
50
+ │ └── lib/
51
+ │ └── utils.ts ← shared helpers (cn, etc.)
52
+ ├── public/
53
+ │ └── {static assets}
54
+ ├── index.html
55
+ ├── vite.config.ts
56
+ ├── tailwind.config.ts
57
+ └── package.json
58
+ ```
59
+
60
+ ## Vite Config
61
+
62
+ `vite.config.ts`:
63
+
64
+ ```ts
65
+ import { defineConfig } from 'vite'
66
+ import react from '@vitejs/plugin-react'
67
+ import tailwindcss from '@tailwindcss/vite'
68
+
69
+ export default defineConfig({
70
+ plugins: [react(), tailwindcss()],
71
+ })
72
+ ```
73
+
74
+ ## Tailwind Config
75
+
76
+ `tailwind.config.ts`:
77
+
78
+ ```ts
79
+ export default {
80
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
81
+ theme: {
82
+ extend: {
83
+ colors: {
84
+ bg: 'var(--color-bg)',
85
+ fg: 'var(--color-fg)',
86
+ accent: 'var(--color-accent)',
87
+ muted: 'var(--color-muted)',
88
+ },
89
+ fontFamily: {
90
+ display: ['var(--font-display)', 'serif'],
91
+ body: ['var(--font-body)', 'sans-serif'],
92
+ },
93
+ },
94
+ },
95
+ }
96
+ ```
97
+
98
+ CSS variables and font imports in `src/index.css`:
99
+
100
+ ```css
101
+ @import url('https://fonts.googleapis.com/css2?family={DisplayFont}:wght@400;600;700;800&family={BodyFont}:wght@400;500;600&display=swap');
102
+
103
+ @tailwind base;
104
+ @tailwind components;
105
+ @tailwind utilities;
106
+
107
+ :root {
108
+ --color-bg: #xxxxxx;
109
+ --color-fg: #xxxxxx;
110
+ --color-accent: #xxxxxx;
111
+ --color-muted: #xxxxxx;
112
+ --font-display: '{DisplayFont}', serif;
113
+ --font-body: '{BodyFont}', sans-serif;
114
+ }
115
+
116
+ body {
117
+ font-family: var(--font-body);
118
+ }
119
+ ```
120
+
121
+ ## Router Setup
122
+
123
+ `src/App.tsx`:
124
+
125
+ ```tsx
126
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom'
127
+ import Landing from './pages/Landing'
128
+ import AppLayout from './components/app/AppLayout'
129
+ import Dashboard from './pages/Dashboard'
130
+ import Feature1 from './pages/{Feature1}'
131
+ import Feature2 from './pages/{Feature2}'
132
+ import Settings from './pages/Settings'
133
+
134
+ const router = createBrowserRouter([
135
+ { path: '/', element: <Landing /> },
136
+ {
137
+ element: <AppLayout />,
138
+ children: [
139
+ { path: '/dashboard', element: <Dashboard /> },
140
+ { path: '/{feature-1}', element: <Feature1 /> },
141
+ { path: '/{feature-2}', element: <Feature2 /> },
142
+ { path: '/settings', element: <Settings /> },
143
+ ],
144
+ },
145
+ ])
146
+
147
+ export default function App() {
148
+ return <RouterProvider router={router} />
149
+ }
150
+ ```
151
+
152
+ ## App Layout
153
+
154
+ `src/components/app/AppLayout.tsx`:
155
+
156
+ ```tsx
157
+ import { Outlet } from 'react-router-dom'
158
+ import Sidebar from './Sidebar'
159
+
160
+ export default function AppLayout() {
161
+ return (
162
+ <div className="flex min-h-screen bg-bg text-fg">
163
+ <Sidebar />
164
+ <main className="flex-1 p-6 lg:p-8">
165
+ <Outlet />
166
+ </main>
167
+ </div>
168
+ )
169
+ }
170
+ ```
171
+
172
+ ## Waitlist — Formspree Integration
173
+
174
+ Since React + Vite has no backend, use Formspree for email collection.
175
+
176
+ `src/components/landing/WaitlistForm.tsx`:
177
+
178
+ ```tsx
179
+ import { useState, FormEvent } from 'react'
180
+
181
+ // Replace YOUR_FORM_ID with your Formspree form ID
182
+ // Create one free at https://formspree.io
183
+ const FORMSPREE_URL = 'https://formspree.io/f/YOUR_FORM_ID'
184
+
185
+ export default function WaitlistForm() {
186
+ const [email, setEmail] = useState('')
187
+ const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
188
+ const [message, setMessage] = useState('')
189
+
190
+ async function handleSubmit(e: FormEvent) {
191
+ e.preventDefault()
192
+ setStatus('loading')
193
+
194
+ try {
195
+ const res = await fetch(FORMSPREE_URL, {
196
+ method: 'POST',
197
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
198
+ body: JSON.stringify({ email }),
199
+ })
200
+
201
+ if (res.ok) {
202
+ setStatus('success')
203
+ setMessage("You're on the list! We'll notify you when we launch.")
204
+ } else {
205
+ throw new Error()
206
+ }
207
+ } catch {
208
+ setStatus('error')
209
+ setMessage('Something went wrong. Please try again.')
210
+ }
211
+ }
212
+
213
+ if (status === 'success') {
214
+ return <p className="text-green-600 font-medium text-lg">{message}</p>
215
+ }
216
+
217
+ return (
218
+ <form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 max-w-md">
219
+ <input
220
+ type="email"
221
+ value={email}
222
+ onChange={e => setEmail(e.target.value)}
223
+ placeholder="you@example.com"
224
+ required
225
+ className="flex-1 px-4 py-3 rounded-lg border border-muted/30 bg-bg focus:ring-2 focus:ring-accent focus:outline-none"
226
+ />
227
+ <button
228
+ type="submit"
229
+ disabled={status === 'loading'}
230
+ className="px-6 py-3 bg-accent text-white rounded-xl font-medium hover:opacity-90 transition-all disabled:opacity-50"
231
+ >
232
+ {status === 'loading' ? 'Joining...' : 'Join Waitlist'}
233
+ </button>
234
+ {status === 'error' && <p className="text-red-500 text-sm">{message}</p>}
235
+ </form>
236
+ )
237
+ }
238
+ ```
239
+
240
+ ## Mock Data
241
+
242
+ Centralize all fake data in `src/data/mock.ts`:
243
+
244
+ ```ts
245
+ export const features = [
246
+ { icon: '🎯', title: 'Feature One', description: 'Short benefit-driven description.' },
247
+ { icon: '⚡', title: 'Feature Two', description: 'Short benefit-driven description.' },
248
+ { icon: '🔒', title: 'Feature Three', description: 'Short benefit-driven description.' },
249
+ ]
250
+
251
+ export const testimonials = [
252
+ { quote: 'Realistic testimonial here.', name: 'Jane Smith', role: 'CTO, TechCo' },
253
+ // ...
254
+ ]
255
+
256
+ export const pricingPlans = [
257
+ { name: 'Free', price: '$0', features: ['Feature A', 'Feature B'], cta: 'Get Started' },
258
+ { name: 'Pro', price: '$29/mo', features: ['Everything in Free', 'Feature C'], cta: 'Get Pro', popular: true },
259
+ { name: 'Enterprise', price: 'Custom', features: ['Everything in Pro', 'Priority support'], cta: 'Contact Us' },
260
+ ]
261
+
262
+ // Dashboard mock data
263
+ export const dashboardStats = [
264
+ { label: 'Total Users', value: '2,847', change: '+12%' },
265
+ // ...
266
+ ]
267
+
268
+ export const recentActivity = [
269
+ { user: 'Jane Smith', action: 'created a new project', time: '2 hours ago' },
270
+ // ...
271
+ ]
272
+ ```
273
+
274
+ ## Component Patterns
275
+
276
+ - Use functional components with TypeScript.
277
+ - Use `useState` for local state, no state management library needed.
278
+ - Navigation: `<Link to="/dashboard">` from react-router-dom.
279
+ - Keep components focused — one file per component.
280
+ - Props typed with interfaces or inline.
281
+
282
+ ## Dev Server
283
+
284
+ ```bash
285
+ npm run dev
286
+ # Runs on http://localhost:5173
287
+ ```
@@ -120,24 +120,25 @@ Write findings to `tasks/perf-findings.md`:
120
120
 
121
121
  ## Critical
122
122
 
123
- - **[FILE:LINE]** Description
123
+ - [ ] **[FILE:LINE]** Description
124
124
  **Impact:** What happens at scale
125
125
  **Recommendation:** How to fix
126
+ - [x] **[FILE:LINE]** Description *(resolved)*
126
127
 
127
128
  ## High
128
129
 
129
- - **[FILE:LINE]** Description
130
+ - [ ] **[FILE:LINE]** Description
130
131
  **Impact:** ...
131
132
  **Recommendation:** ...
132
133
 
133
134
  ## Medium
134
135
 
135
- - **[FILE:LINE]** Description
136
+ - [ ] **[FILE:LINE]** Description
136
137
  **Recommendation:** ...
137
138
 
138
139
  ## Low
139
140
 
140
- - **[FILE:LINE]** Description
141
+ - [ ] **[FILE:LINE]** Description
141
142
  **Recommendation:** ...
142
143
 
143
144
  ## Passed Checks
@@ -146,13 +147,13 @@ Write findings to `tasks/perf-findings.md`:
146
147
 
147
148
  ## Summary
148
149
 
149
- | Severity | Count |
150
- |----------|-------|
151
- | Critical | N |
152
- | High | N |
153
- | Medium | N |
154
- | Low | N |
155
- | **Total** | **N** |
150
+ | Severity | Open | Resolved this run |
151
+ |----------|------|-------------------|
152
+ | Critical | N | N |
153
+ | High | N | N |
154
+ | Medium | N | N |
155
+ | Low | N | N |
156
+ | **Total** | **N** | **N** |
156
157
  ```
157
158
 
158
159
  **Never overwrite** `tasks/perf-findings.md` — append new audits with a date header.
@@ -0,0 +1,283 @@
1
+ ---
2
+ name: sk:seo-audit
3
+ description: "SEO audit for web projects. Dual-mode: scans source templates + optionally fetches from running dev server. Ask-before-fix for mechanical issues. Outputs checklist findings to tasks/seo-findings.md."
4
+ license: Complete terms in LICENSE.txt
5
+ ---
6
+
7
+ # /sk:seo-audit
8
+
9
+ ## Purpose
10
+
11
+ Standalone optional command — audits any web project for SEO issues regardless of framework (Laravel, Next.js, Nuxt, plain HTML, etc.). Run at any point after implementation is complete. NOT a numbered workflow step — invoke it independently like `/sk:debug`.
12
+
13
+ Two modes:
14
+ - **Source mode** (always runs): scans template files directly for SEO signals
15
+ - **Server mode** (optional): fetches from a running dev server to validate rendered output
16
+
17
+ Run when: before shipping a client site, after adding new pages, or any time you want to check SEO health.
18
+
19
+ ## Hard Rules
20
+
21
+ - **Never auto-apply fixes without explicit user confirmation.**
22
+ - **Every finding must cite a specific `file:line`.**
23
+ - **Every finding is a checkbox:** `- [ ]` (open) or `- [x]` (auto-fixed this run)
24
+ - **Append to `tasks/seo-findings.md`** — never overwrite (use date header per run)
25
+ - **Degrade gracefully** if no server is running — skip Phase 2, note it in report
26
+ - **Structured data validation requires external tools** (Google Rich Results Test) — flag it, don't skip silently
27
+
28
+ ## Before You Start
29
+
30
+ 1. Read `tasks/findings.md` if it exists — look for site context, target audience, business type (helps tailor content strategy recommendations)
31
+ 2. Read `tasks/lessons.md` if it exists — apply any SEO-related lessons
32
+ 3. Check if `tasks/seo-findings.md` exists — if yes, read the last dated section to identify previously flagged items (used to populate "Passed Checks" in the new report)
33
+
34
+ ## Mode Detection
35
+
36
+ ### Source Mode — Always Active
37
+
38
+ Scan the project for template files:
39
+
40
+ | Extension | Framework |
41
+ |-----------|-----------|
42
+ | `.blade.php` | Laravel |
43
+ | `.jsx`, `.tsx` | React / Next.js |
44
+ | `.vue` | Vue / Nuxt |
45
+ | `.html` | Plain HTML / static |
46
+ | `.ejs` | Express / Node |
47
+ | `.njk` | Nunjucks |
48
+ | `.twig` | Twig / Symfony |
49
+ | `.erb` | Ruby on Rails |
50
+ | `.astro` | Astro |
51
+
52
+ Print: `"Source mode: found N template files ([extensions detected])"`
53
+
54
+ ### Server Mode — Optional
55
+
56
+ Probe ports in parallel (background curl processes) to avoid 14-second worst-case serial timeout:
57
+ - Ports: 3000, 5173, 8000, 8080, 4321, 4000, 8888
58
+ - Command: `curl -s -I --max-time 2 http://localhost:PORT` (HEAD request to capture both status code and headers)
59
+ - Use the first port that returns HTTP 200 **and** has a `Content-Type: text/html` response header
60
+
61
+ If a port returns 200 but no `Content-Type: text/html` header, skip it — it is likely a non-HTTP service (e.g., a database, gRPC server) and not a web app. Try the next port.
62
+
63
+ If any port qualifies: `"Server mode: detected running dev server at http://localhost:PORT"`
64
+
65
+ If none respond or qualify: `"Server mode: no dev server detected — skipping Phase 2. Start your dev server and re-run for full audit."`
66
+
67
+ > Note: confirm the detected URL looks correct before trusting Phase 2 results.
68
+
69
+ ## Phase 1 — Source Audit
70
+
71
+ ### Technical SEO
72
+
73
+ - `robots.txt` — exists in project root or `public/`; does NOT contain `Disallow: /` blocking all crawlers
74
+ - `sitemap.xml` — exists in project root or `public/`; referenced in `robots.txt` via `Sitemap:` directive
75
+ - `<html lang="">` — present on all layout/root templates (not empty)
76
+ - Canonical tags — `<link rel="canonical">` present on key page templates
77
+ - No accidental `<meta name="robots" content="noindex">` on public-facing pages
78
+ - No hardcoded `http://` asset URLs in templates (mixed content risk)
79
+
80
+ ### On-Page SEO
81
+
82
+ - `<title>` — present in `<head>`, unique across pages, 50–60 characters
83
+ - `<meta name="description">` — present in `<head>`, unique across pages, 150–160 characters
84
+ - Exactly one `<h1>` per page template (not zero, not two+)
85
+ - Heading hierarchy not skipped (no jumping from `<h2>` to `<h4>`)
86
+ - All `<img>` tags have `alt` attribute (even if empty for decorative — but flag empty alt on non-decorative images)
87
+ - Internal `<a>` link text is descriptive — flag anchors with text: "click here", "here", "read more", "link", "this"
88
+ - Image filenames are descriptive — flag patterns like `img001`, `IMG_`, `photo`, `image`, `DSC_`, `screenshot` with no context
89
+
90
+ ### Content Signals
91
+
92
+ - Open Graph tags: `og:title`, `og:description`, `og:url`, `og:image` all present in layout
93
+ - Twitter Card tags: `twitter:card` present
94
+ - JSON-LD structured data block: look for `<script type="application/ld+json">` — note presence/absence; do NOT validate schema (requires external tool)
95
+ - Page `<html lang="">` matches expected locale
96
+
97
+ ## Phase 2 — Server Audit (Optional)
98
+
99
+ If server detected:
100
+
101
+ 1. Fetch `/` and discover up to 4 additional pages (from `<a>` href values in homepage, or from sitemap.xml)
102
+ 2. For each page fetched, extract and compare:
103
+ - Rendered `<title>` vs source template value
104
+ - Rendered `<meta name="description">` vs source template value
105
+ - Rendered `<h1>` vs source template value
106
+ - Rendered OG tags vs source template
107
+ 3. Flag mismatches: `"/about — Source template declares <title>About Us</title> but rendered output shows <title>My App</title> — framework may be overriding"`
108
+ 4. Check HTTP status codes — flag any key page returning non-200
109
+ 5. Check for redirect chains on common pages (/ → /home → /index is a chain)
110
+
111
+ > Note in report: "Structured data detected but NOT validated — use Google Rich Results Test (https://search.google.com/test/rich-results) to verify schema markup."
112
+
113
+ ## Phase 3 — Ask Before Fix
114
+
115
+ After completing Phase 1 (and Phase 2 if run):
116
+
117
+ 1. Collect all auto-fixable findings (see Mechanical Fixes Reference below)
118
+ 2. Display numbered list:
119
+
120
+ ```
121
+ Found N auto-fixable issues:
122
+ 1. Missing <title> in resources/views/layouts/app.blade.php
123
+ 2. Missing alt attribute on <img> in resources/views/home.blade.php:42
124
+ 3. Missing robots.txt
125
+ ... (all N items)
126
+
127
+ Apply mechanical fixes? [y/N]
128
+ ```
129
+
130
+ 3. Wait for user response
131
+ 4. On `y`: apply each fix in order, log `"Fixed: [description] in [file:line]"`, mark as `- [x]` in report. On individual fix failure: log the error, mark that item `- [ ]`, and continue with remaining fixes.
132
+ 5. On `n`: mark all as `- [ ]` in report with Fix instructions
133
+
134
+ ## Mechanical Fixes Reference
135
+
136
+ What this skill CAN auto-apply when user confirms:
137
+
138
+ | Issue | Fix Applied |
139
+ |-------|------------|
140
+ | Missing `<title>` in `<head>` | Add `<title>TODO: Add page title (50-60 chars)</title>` |
141
+ | Missing `<meta name="description">` | Add `<meta name="description" content="TODO: Add description (150-160 chars)">` |
142
+ | `<img>` missing `alt` attribute | Add `alt="TODO: Describe this image for screen readers"` |
143
+ | Missing `<link rel="canonical">` | Add `<link rel="canonical" href="TODO: Add canonical URL">` |
144
+ | Missing `robots.txt` | Create `robots.txt`: `User-agent: *\nAllow: /\nSitemap: /sitemap.xml` |
145
+ | Missing `sitemap.xml` | Create `sitemap.xml` scaffold with homepage entry |
146
+ | Multiple `<h1>` on same page | Demote 2nd, 3rd... `<h1>` to `<h2>` |
147
+ | Missing OG tags | Add `og:title`, `og:description`, `og:url` block (with TODO placeholders) |
148
+ | Missing `<html lang="">` | Add `lang="en"` — **note in output: verify correct language code** |
149
+
150
+ Things this skill CANNOT auto-apply (report only):
151
+ - Content quality improvements
152
+ - Keyword targeting
153
+ - Title/description CONTENT (only adds TODOs)
154
+ - Schema markup content (only flags missing)
155
+ - Backlink strategy
156
+ - `<meta name="robots" content="noindex">` removal — only the developer can confirm whether a page is intentionally noindexed
157
+
158
+ ## Generate Report
159
+
160
+ Write to `tasks/seo-findings.md` — append with date header, never overwrite.
161
+
162
+ ```markdown
163
+ # SEO Audit — YYYY-MM-DD
164
+
165
+ **Mode:** Source only | Source + Server (`http://localhost:PORT`)
166
+ **Templates scanned:** N files ([detected extensions])
167
+ **Pages fetched:** N | none — server not detected
168
+
169
+ ---
170
+
171
+ ## Critical
172
+
173
+ - [x] `resources/views/layouts/app.blade.php` — Missing `<title>` tag *(auto-fixed — add real title)*
174
+ - [ ] `resources/views/about.blade.php:1` — Missing `<meta name="description">`
175
+ **Impact:** Google may auto-generate a description from page content, often poorly.
176
+ **Fix:** Add `<meta name="description" content="150-160 char description">` in `<head>`
177
+
178
+ ## High
179
+
180
+ - [ ] `public/robots.txt` — File missing
181
+ **Impact:** Search engines have no crawl guidance — may index unwanted pages.
182
+ **Fix:** Create `robots.txt` with `User-agent: *`, `Allow: /`, `Sitemap:` directive
183
+
184
+ ## Medium
185
+
186
+ - [ ] `resources/views/home.blade.php:42` — `<img src="hero.jpg">` missing alt attribute
187
+ **Impact:** Accessibility violation + missed keyword opportunity.
188
+ **Fix:** Add descriptive `alt="..."` text
189
+
190
+ ## Low
191
+
192
+ - [ ] Image filename `IMG_4521.jpg` — not descriptive
193
+ **Impact:** Minor missed keyword signal.
194
+ **Fix:** Rename to describe the image content
195
+
196
+ ## Content Strategy — Manual Action
197
+
198
+ - [ ] No JSON-LD structured data detected — consider adding schema markup (Article / Product / LocalBusiness / FAQPage) based on your content type. Validate at: https://search.google.com/test/rich-results
199
+ - [ ] `og:image` missing — social shares will have no preview image. Add a default OG image in your layout.
200
+ - [ ] Submit `sitemap.xml` to Google Search Console for faster indexing
201
+ - [ ] Title tags are present but content is generic ("TODO") — research target keywords for each page
202
+
203
+ ## Passed Checks
204
+
205
+ - `robots.txt` exists and allows crawling *(was: missing — fixed in 2026-03-10 audit)*
206
+ - All `<img>` tags have alt attributes
207
+ - Single `<h1>` per page
208
+
209
+ (or "First run — no prior baseline to compare against")
210
+
211
+ ## Applied Fixes
212
+
213
+ - Fixed: Added `<title>` placeholder to `resources/views/layouts/app.blade.php`
214
+ - Fixed: Created `public/robots.txt`
215
+
216
+ (or "No fixes applied this run")
217
+
218
+ ---
219
+
220
+ ## Summary
221
+
222
+ | Severity | Open | Fixed this run |
223
+ |----------|------|----------------|
224
+ | Critical | 1 | 1 |
225
+ | High | 1 | 0 |
226
+ | Medium | 3 | 0 |
227
+ | Low | 2 | 0 |
228
+ | Content Strategy | 4 | — |
229
+ | **Total** | **11** | **1** |
230
+ ```
231
+
232
+ **Never overwrite** `tasks/seo-findings.md` — append new audits with a date header.
233
+
234
+ ## When Done
235
+
236
+ If Critical or High items are open:
237
+ > "SEO audit complete. **N critical/high issues** need attention before this site will rank well. Findings and checklist in `tasks/seo-findings.md`."
238
+
239
+ If only Medium/Low/Content Strategy open:
240
+ > "Technical SEO is solid. **N medium/low polish items** and **N content strategy items** noted in `tasks/seo-findings.md`. Check off items as you address them."
241
+
242
+ If all clean:
243
+ > "SEO audit passed — no issues found. `tasks/seo-findings.md` updated with clean baseline."
244
+
245
+ If fixes were declined (`n`):
246
+ > "SEO audit complete. **N auto-fixable issues** left open (fixes declined). Checklist in `tasks/seo-findings.md` — check off items as you manually address them."
247
+
248
+ ---
249
+
250
+ ## Fix & Retest Protocol
251
+
252
+ When applying an SEO fix, classify it before committing:
253
+
254
+ **a. Template/config change** (adding a meta tag, fixing alt text, scaffolding robots.txt, adding lang attribute, creating sitemap.xml) → commit and re-run `/sk:seo-audit`. No test update needed.
255
+
256
+ **b. Logic change** (changing how a framework generates meta tags, modifying a layout component's data-fetching or rendering logic, changing routing that affects canonical URLs) → trigger protocol:
257
+ 1. Update or add failing unit tests for the new behavior
258
+ 2. Re-run `/sk:test` — must pass at 100% coverage
259
+ 3. Commit (tests + fix together in one commit)
260
+ 4. Re-run `/sk:seo-audit` to verify the fix resolved the finding
261
+
262
+ **Common logic-change SEO fixes:**
263
+ - Changing a Next.js `generateMetadata()` function → update tests asserting metadata output
264
+ - Modifying a Laravel controller that sets page title → update feature tests
265
+ - Changing a Vue component that injects `<head>` tags → update component tests
266
+
267
+ ---
268
+
269
+ ## Model Routing
270
+
271
+ Read `.shipkit/config.json` from the project root if it exists.
272
+
273
+ - If `model_overrides["sk:seo-audit"]` is set, use that model — it takes precedence.
274
+ - Otherwise use the `profile` field. Default: `balanced`.
275
+
276
+ | Profile | Model |
277
+ |---------|-------|
278
+ | `full-sail` | sonnet |
279
+ | `quality` | sonnet |
280
+ | `balanced` | sonnet |
281
+ | `budget` | haiku |
282
+
283
+ > `opus` = inherit (uses the current session model). When spawning sub-agents via the Agent tool, pass `model: "<resolved-model>"`.
@@ -145,16 +145,54 @@ If a frontend stack was detected, generate FE test files:
145
145
 
146
146
  Skip this step if no FE stack was detected.
147
147
 
148
- ### 8b. Playwright-Specific (conditional)
148
+ ### 8b. Write E2E Spec Files (conditional)
149
+
150
+ **Only if `playwright.config.ts` or `playwright.config.js` is detected in the project root:**
151
+
152
+ Write `e2e/<feature>.spec.ts` files covering the acceptance criteria from `tasks/todo.md`. Follow these rules:
153
+
154
+ - Use `test.describe` / `test` blocks — not `describe`/`it`
155
+ - Use role-based locators: `getByRole`, `getByLabel`, `getByText`, `getByPlaceholder` — never CSS selectors
156
+ - Use `test.beforeEach` for shared setup (auth, navigation)
157
+ - Use `test.skip(!email, 'ENV_VAR not set — skipping')` guards for credential-dependent tests
158
+ - Auth credentials from env vars via `e2e/helpers/auth.ts` — never hardcode credentials
159
+ - Soft assertions (`expect.soft`) for non-critical checks; hard `expect` for gate conditions
160
+
161
+ E2E spec example structure:
162
+ ```ts
163
+ import { test, expect } from '@playwright/test'
164
+ import { signIn, TEST_USERS } from './helpers/auth'
165
+
166
+ test.describe('[Feature] — [scenario]', () => {
167
+ test.beforeEach(async ({ page }) => {
168
+ const { email, password } = TEST_USERS.regular
169
+ test.skip(!email, 'E2E_USER_EMAIL not set — skipping')
170
+ await signIn(page, email, password)
171
+ })
172
+
173
+ test('[behavior description]', async ({ page }) => {
174
+ await page.goto('/dashboard/feature')
175
+ await expect(page.getByRole('heading', { name: /title/i })).toBeVisible()
176
+ })
177
+ })
178
+ ```
179
+
180
+ Create `e2e/helpers/auth.ts` if it doesn't exist (see `/sk:e2e` Playwright Setup Reference).
181
+
182
+ **Run the E2E spec to confirm tests fail or skip** (they should fail until implementation, or skip if env vars aren't set — both are acceptable for the RED phase):
183
+ ```bash
184
+ npx playwright test e2e/<feature>.spec.ts --reporter=list
185
+ ```
186
+
187
+ ### 8c. Playwright MCP Inspection (optional)
149
188
 
150
- **Only if `@playwright/sk:test` is detected:**
189
+ **Only if the Playwright MCP plugin is active in the session AND live selectors are needed:**
151
190
 
152
191
  Use the Playwright MCP plugin to inspect live page state for more accurate selectors:
153
192
 
154
193
  1. Navigate to target URL
155
194
  2. Capture accessibility snapshot for role-based selectors
156
195
  3. Screenshot for visual reference
157
- 4. Optionally run inline assertions for complex interactions
158
196
 
159
197
  ### 9. Verify Tests Fail (Red Phase)
160
198
 
@@ -170,6 +208,7 @@ Output:
170
208
  ```
171
209
  BE tests written: X tests in Y files ([framework])
172
210
  FE tests written: X tests in Y files ([framework]) ← omit if no FE stack
211
+ E2E specs written: X tests in Y files (Playwright) ← omit if no playwright.config.ts
173
212
  Existing tests updated: X files
174
213
  Status: RED (tests fail as expected — ready for implementation)
175
214
  ```