$npx -y skills add PramodDutta/qaskills --skill add-seed-skillsUse when adding or editing QA skills in seed-skills/ or getting them onto the live qaskills.sh catalog, e.g. "add N new skills", "create a seed skill for X", "seed the database", "the skill page is empty", "skill 404s on the site".
| 1 | # Add Seed Skills |
| 2 | |
| 3 | Pipeline: `seed-skills/<slug>/SKILL.md` -> validator -> `seed.ts` upsert into the PROD database -> live catalog. These are PRODUCT catalog skills (Zod schema with testingTypes/languages), not Claude Code workflow skills; never confuse the two formats. |
| 4 | |
| 5 | ## Format contract (parser reality) |
| 6 | |
| 7 | `packages/web/src/db/seed.ts` parses frontmatter with regex, not a YAML library: |
| 8 | - Every value on ONE line; a wrapped description parses as truncated garbage |
| 9 | - Arrays INLINE ONLY: `tags: [a, b, c]`. YAML block lists (`- item`) parse as EMPTY arrays |
| 10 | - Zod limits: name 1-100 chars, description 10-500 chars, version semver, `testingTypes` >= 1, `languages` >= 1 |
| 11 | - Allowed values come from `packages/shared/src/constants/` (testing types, frameworks, languages, domains, agent slugs); check there before inventing one |
| 12 | |
| 13 | Template (model the body on `seed-skills/playwright-e2e/SKILL.md`): |
| 14 | |
| 15 | ```markdown |
| 16 | --- |
| 17 | name: Human Readable Skill Name |
| 18 | description: One line, 10-500 chars, what the skill teaches an agent to do. |
| 19 | version: 1.0.0 |
| 20 | author: thetestingacademy |
| 21 | license: MIT |
| 22 | tags: [tag-one, tag-two] |
| 23 | testingTypes: [e2e] |
| 24 | frameworks: [playwright] |
| 25 | languages: [typescript] |
| 26 | domains: [web] |
| 27 | agents: [claude-code, cursor, github-copilot, windsurf, cline] |
| 28 | --- |
| 29 | |
| 30 | # Skill Title |
| 31 | |
| 32 | Real instructions: principles, project structure, code samples, checklists. |
| 33 | This body becomes fullDescription: it renders on the skill page and is what |
| 34 | the CLI downloads. Aim for 100+ lines of substance; an empty or thin body |
| 35 | is a broken product page. |
| 36 | ``` |
| 37 | |
| 38 | ## Steps |
| 39 | |
| 40 | ### 1. Dedup the slug (directory name = slug) |
| 41 | |
| 42 | ```bash |
| 43 | ls /Users/promode/qaskills/seed-skills | grep -i "<core-term>" |
| 44 | curl -s -o /dev/null -w '%{http_code}\n' "https://qaskills.sh/api/skills/<slug>" # want 404 |
| 45 | ``` |
| 46 | |
| 47 | ### 2. Write `seed-skills/<slug>/SKILL.md` |
| 48 | |
| 49 | Frontmatter per the contract above, then the full body. |
| 50 | |
| 51 | ### 3. Validate every file |
| 52 | |
| 53 | ```bash |
| 54 | cd /Users/promode/qaskills |
| 55 | pnpm --filter @qaskills/shared build && pnpm --filter @qaskills/skill-validator build |
| 56 | for d in <slug-one> <slug-two>; do |
| 57 | node packages/skill-validator/dist/cli.js "seed-skills/$d/SKILL.md" || echo "INVALID: $d" |
| 58 | done |
| 59 | ``` |
| 60 | |
| 61 | Also eyeball that arrays are inline and the description is one line (the validator uses the real YAML parser and can pass files the seed regex still mangles). |
| 62 | |
| 63 | ### 4. Seed PRODUCTION (the dangerous step) |
| 64 | |
| 65 | **Never use the `DATABASE_URL` from `.env.local`.** It points at a stale non-prod database; seeding it changes nothing on the live site. This mistake has burned a full session before. |
| 66 | |
| 67 | 1. Baseline: `curl -s 'https://qaskills.sh/api/skills?limit=1'` and record the top-level `total`. |
| 68 | 2. Get the prod URL: run `vercel env pull .env.vercel-prod --environment=production` yourself (worked from the agent as of 2026-07-07; the file is gitignored). If the pull is blocked, ask the user to run that exact command and wait. |
| 69 | 3. Seed with the explicit URL (strip any surrounding quotes from the value): |
| 70 | |
| 71 | ```bash |
| 72 | cd /Users/promode/qaskills |
| 73 | export DATABASE_URL='<prod-url-without-quotes>' |
| 74 | pnpm --filter @qaskills/web db:seed |
| 75 | ``` |
| 76 | |
| 77 | `seed.ts` is an upsert (`onConflictDoUpdate` on skills): safe to re-run, it will not delete the live rows that exist only in prod. Never substitute custom SQL, and never run UPDATE/DELETE against prod without explicit user approval. |
| 78 | |
| 79 | ### 5. Verify live (the only proof that counts) |
| 80 | |
| 81 | ```bash |
| 82 | curl -s 'https://qaskills.sh/api/skills?limit=1' # total must equal baseline + N |
| 83 | curl -s "https://qaskills.sh/api/skills/<slug>/content" | head -20 # frontmatter + body, not empty |
| 84 | curl -s -o /dev/null -w '%{http_code}\n' "https://qaskills.sh/skills/thetestingacademy/<slug>" # 200 (adjust author segment if different) |
| 85 | ``` |
| 86 | |
| 87 | `total` unchanged, or slug 404s => you seeded the wrong database. STOP. Do not retry blindly; re-verify which URL was exported and report the mismatch. |
| 88 | |
| 89 | ### 6. Commit |
| 90 | |
| 91 | ```bash |
| 92 | git -C /Users/promode/qaskills add seed-skills/<slug-one> seed-skills/<slug-two> |
| 93 | git -C /Users/promode/qaskills commit -m "feat(skills): add <N> <theme> seed skills" |
| 94 | git -C /Users/promode/qaskills push origin main |
| 95 | ``` |
| 96 | |
| 97 | No web redeploy is needed for catalog changes (pages read the DB), but commit so the repo stays the source of truth. |
| 98 | |
| 99 | ## Failure modes |
| 100 | |
| 101 | | Symptom | Cause | Fix | |
| 102 | |---|---|---| |
| 103 | | Skill live but tags/types empty | Block-list arrays or multi-line values in frontmatter | Convert to inline arrays, single lines, re-seed | |
| 104 | | Skill page renders only the short description | Missing/empty markdown body | Write the body, re-seed (upsert refreshes fullDescription) | |
| 105 | | Live `total` did not grow | Seeded the stale `.env.local` DB | Step 4.2, re-seed with the real prod URL | |
| 106 | | Validator passes but seed drops fields | Validator parses re |