sanook-cli 0.4.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/.env.example +23 -0
- package/CHANGELOG.md +38 -0
- package/LICENSE +201 -0
- package/README.md +239 -0
- package/dist/agentContext.js +2 -0
- package/dist/approval.js +78 -0
- package/dist/bin.js +461 -0
- package/dist/brain.js +186 -0
- package/dist/commands.js +66 -0
- package/dist/compaction.js +85 -0
- package/dist/config.js +101 -0
- package/dist/cost.js +59 -0
- package/dist/diff.js +36 -0
- package/dist/gateway/auth.js +32 -0
- package/dist/gateway/ledger.js +94 -0
- package/dist/gateway/lock.js +114 -0
- package/dist/gateway/schedule.js +74 -0
- package/dist/gateway/scheduler.js +87 -0
- package/dist/gateway/serve.js +57 -0
- package/dist/gateway/server.js +94 -0
- package/dist/gateway/telegram.js +115 -0
- package/dist/git.js +55 -0
- package/dist/hooks.js +104 -0
- package/dist/knowledge.js +68 -0
- package/dist/loop.js +169 -0
- package/dist/mcp.js +191 -0
- package/dist/memory.js +108 -0
- package/dist/providers/codex.js +86 -0
- package/dist/providers/keys.js +37 -0
- package/dist/providers/models.js +55 -0
- package/dist/providers/registry.js +241 -0
- package/dist/session.js +36 -0
- package/dist/skill-install.js +190 -0
- package/dist/skills.js +111 -0
- package/dist/tools/bash.js +26 -0
- package/dist/tools/edit.js +107 -0
- package/dist/tools/git.js +68 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/list.js +24 -0
- package/dist/tools/permission.js +30 -0
- package/dist/tools/read.js +18 -0
- package/dist/tools/recall.js +12 -0
- package/dist/tools/remember.js +14 -0
- package/dist/tools/schedule.js +61 -0
- package/dist/tools/search.js +54 -0
- package/dist/tools/skill.js +65 -0
- package/dist/tools/task.js +46 -0
- package/dist/tools/util.js +5 -0
- package/dist/tools/write.js +27 -0
- package/dist/ui/app.js +132 -0
- package/dist/ui/banner.js +20 -0
- package/dist/ui/brain-wizard.js +29 -0
- package/dist/ui/render.js +57 -0
- package/dist/ui/setup.js +46 -0
- package/package.json +77 -0
- package/second-brain/AGENTS.md +18 -0
- package/second-brain/CLAUDE.md +96 -0
- package/second-brain/Evals/retrieval-eval.md +30 -0
- package/second-brain/GEMINI.md +15 -0
- package/second-brain/Home.md +33 -0
- package/second-brain/README.md +29 -0
- package/second-brain/Runbooks/ingest-quarantine.md +27 -0
- package/second-brain/Runbooks/sleep-time-consolidation.md +26 -0
- package/second-brain/Shared/AI-Context-Index.md +52 -0
- package/second-brain/Shared/Core-Facts/protected-facts.md +21 -0
- package/second-brain/Shared/Decision-Memory/decision-log.md +24 -0
- package/second-brain/Shared/Memory-Inbox/memory-inbox.md +23 -0
- package/second-brain/Shared/Operating-State/current-state.md +30 -0
- package/second-brain/Shared/Provenance/ingest-log.md +27 -0
- package/second-brain/Shared/Rules/context-assembly-policy.md +28 -0
- package/second-brain/Shared/Rules/frontmatter-standard.md +33 -0
- package/second-brain/Shared/Rules/skills-admission.md +30 -0
- package/second-brain/Shared/User-Memory/user-preferences.md +25 -0
- package/second-brain/Templates/bug.md +22 -0
- package/second-brain/Templates/handoff.md +21 -0
- package/second-brain/Templates/project.md +24 -0
- package/second-brain/Templates/session.md +26 -0
- package/second-brain/USER.md +36 -0
- package/second-brain/Vault Structure Map.md +106 -0
- package/skills/agent-tool-mcp-builder/SKILL.md +88 -0
- package/skills/api-design-review/SKILL.md +70 -0
- package/skills/async-concurrency-correctness/SKILL.md +93 -0
- package/skills/audit-accessibility-wcag/SKILL.md +59 -0
- package/skills/audit-technical-seo/SKILL.md +62 -0
- package/skills/auth-jwt-session/SKILL.md +88 -0
- package/skills/brainstorm-design/SKILL.md +73 -0
- package/skills/build-etl-pipeline/SKILL.md +58 -0
- package/skills/build-form-validation/SKILL.md +103 -0
- package/skills/build-office-docs/SKILL.md +80 -0
- package/skills/build-react-component/SKILL.md +116 -0
- package/skills/build-spreadsheet/SKILL.md +106 -0
- package/skills/caching-strategy/SKILL.md +75 -0
- package/skills/cicd-pipeline-author/SKILL.md +65 -0
- package/skills/cloud-cost-optimize/SKILL.md +91 -0
- package/skills/code-comments/SKILL.md +52 -0
- package/skills/code-review/SKILL.md +61 -0
- package/skills/db-migration-safety/SKILL.md +67 -0
- package/skills/debug-frontend-browser/SKILL.md +58 -0
- package/skills/debug-root-cause/SKILL.md +54 -0
- package/skills/dependency-upgrade/SKILL.md +56 -0
- package/skills/deploy-release/SKILL.md +64 -0
- package/skills/diff-table-parity/SKILL.md +58 -0
- package/skills/dockerfile-optimize/SKILL.md +82 -0
- package/skills/error-message/SKILL.md +58 -0
- package/skills/estimate-work/SKILL.md +54 -0
- package/skills/explore-codebase/SKILL.md +73 -0
- package/skills/git-commit-pr/SKILL.md +65 -0
- package/skills/gitops-deploy-workflow/SKILL.md +97 -0
- package/skills/implement-from-design/SKILL.md +69 -0
- package/skills/incident-response-sre/SKILL.md +78 -0
- package/skills/k8s-debug-workload/SKILL.md +135 -0
- package/skills/k8s-manifest-review/SKILL.md +86 -0
- package/skills/llm-eval-harness/SKILL.md +63 -0
- package/skills/manage-client-server-state/SKILL.md +94 -0
- package/skills/mermaid-diagram/SKILL.md +61 -0
- package/skills/message-queue-jobs/SKILL.md +139 -0
- package/skills/naming-helper/SKILL.md +57 -0
- package/skills/observability-instrument/SKILL.md +113 -0
- package/skills/optimize-core-web-vitals/SKILL.md +75 -0
- package/skills/optimize-sql-query/SKILL.md +67 -0
- package/skills/performance-profiling/SKILL.md +65 -0
- package/skills/process-pdf/SKILL.md +107 -0
- package/skills/profile-dataset/SKILL.md +97 -0
- package/skills/prompt-engineering/SKILL.md +70 -0
- package/skills/rag-pipeline/SKILL.md +53 -0
- package/skills/rate-limiting/SKILL.md +96 -0
- package/skills/refactor-cleanup/SKILL.md +54 -0
- package/skills/regex-build/SKILL.md +72 -0
- package/skills/release-notes/SKILL.md +79 -0
- package/skills/rest-graphql-contract/SKILL.md +71 -0
- package/skills/scrape-structured-web-data/SKILL.md +61 -0
- package/skills/secrets-management/SKILL.md +96 -0
- package/skills/security-review/SKILL.md +62 -0
- package/skills/shell-script-robust/SKILL.md +71 -0
- package/skills/style-responsive-tailwind/SKILL.md +70 -0
- package/skills/terraform-plan-review/SKILL.md +95 -0
- package/skills/type-safety-strict/SKILL.md +82 -0
- package/skills/validate-data-quality/SKILL.md +62 -0
- package/skills/wrangle-tabular-data/SKILL.md +75 -0
- package/skills/write-adr/SKILL.md +75 -0
- package/skills/write-analytical-sql/SKILL.md +71 -0
- package/skills/write-data-viz/SKILL.md +58 -0
- package/skills/write-docs/SKILL.md +54 -0
- package/skills/write-plan/SKILL.md +59 -0
- package/skills/write-playwright-e2e/SKILL.md +86 -0
- package/skills/write-prd/SKILL.md +65 -0
- package/skills/write-rfc/SKILL.md +75 -0
- package/skills/write-tests/SKILL.md +50 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-tool-mcp-builder
|
|
3
|
+
description: Designs agent tools and builds MCP servers (tool schemas, naming, error shapes, auth, context-efficient results) when exposing capabilities to an LLM agent or scaffolding a Model Context Protocol server.
|
|
4
|
+
when_to_use: User is building an MCP server, defining tools/function schemas for an agent, or fixing tools that the model misuses, returns bloated results, or mis-routes. NOT for general REST/GraphQL APIs for humans (use rest-graphql-contract).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Reach for this when an LLM agent — not a human — is the caller:
|
|
10
|
+
|
|
11
|
+
- Scaffolding an **MCP server** (stdio or streamable HTTP) that exposes tools/resources/prompts.
|
|
12
|
+
- Defining **tool / function-call schemas** for a model (the `input_schema`, descriptions, result shape).
|
|
13
|
+
- **Fixing tools the model misuses**: wrong tool picked, args malformed, results so large they blow the context window, or the model can't tell success from failure.
|
|
14
|
+
|
|
15
|
+
NOT for human-facing REST/GraphQL APIs — those optimize for client devs and stable contracts, not for a model reading schemas at inference time. Use `rest-graphql-contract` there.
|
|
16
|
+
|
|
17
|
+
Core mental model: **the model is your end user.** It reads only the tool name + description + param schema before deciding. Every result it gets back consumes context it pays for. Design for "a smart reader who sees the schema and nothing else."
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
1. **Pick transport + deployment before writing tools.**
|
|
22
|
+
- **stdio** (subprocess, JSON-RPC over stdin/stdout) — local single-user tools, the default for desktop/CLI agent clients. No auth layer; the OS process boundary is the trust boundary. The host spawns one process per client, so concurrency = multiple independent processes (see step 6).
|
|
23
|
+
- **Streamable HTTP** — remote/multi-tenant, web-hosted. Needs real auth (below) and must handle concurrent requests in one process.
|
|
24
|
+
- Never log to **stdout** on a stdio server — stdout is the JSON-RPC channel and any stray `print` corrupts the protocol. Log to **stderr** only.
|
|
25
|
+
|
|
26
|
+
2. **Choose auth by deployment, not by habit.**
|
|
27
|
+
- stdio local → **none** (process isolation). Read secrets from env vars the host injects; never take API keys as tool params (they land in model context + transcripts).
|
|
28
|
+
- Remote internal → **static bearer / API key** in the `Authorization` header.
|
|
29
|
+
- Remote multi-user → **OAuth 2.0**; keep token refresh server-side. The model never sees the credential — your server injects it at egress when calling the downstream API.
|
|
30
|
+
|
|
31
|
+
3. **Name and scope tools for a model picking from a list.**
|
|
32
|
+
- `verb_noun`, specific: `search_orders`, `create_invoice` — not `orders`, `do`, `query`. The name is the model's primary routing signal.
|
|
33
|
+
- **One tool = one job.** A `manage_users(action=create|delete|list)` mega-tool forces the model to get `action` right *and* shapes a vague schema. Split into `create_user` / `delete_user` / `list_users` so each gets a tight schema and a name the model can match.
|
|
34
|
+
- Keep the active set **focused** (rough ceiling ~20–40 well-named tools). Past that, the model mis-routes. If you have a large library, expose a search/discovery tool and load schemas on demand rather than dumping all of them into context.
|
|
35
|
+
- **bash vs. dedicated tool:** a `bash`/`run_command` tool gives breadth but an opaque string the host can't gate, render, or parallelize. Promote an action to a **dedicated tool** when you need to: gate it (irreversible/external — `send_email`, `delete_*`), enforce an invariant (reject an edit if the file changed since last read), render custom UI, or mark it parallel-safe (read-only `glob`/`grep`). Rule: start with bash for reach, promote when you must gate/render/audit/parallelize.
|
|
36
|
+
|
|
37
|
+
4. **Write the param schema and description for the model.**
|
|
38
|
+
- JSON Schema `input_schema`: `type:"object"`, `properties` with a `description` on **every** field, `required` listing only truly-required params, `enum` for fixed value sets, `additionalProperties:false`.
|
|
39
|
+
- Descriptions are **prescriptive about *when* to call**, not just what the tool does — e.g. *"Call this when the user asks about current order status or recent shipments."* On models that reach for tools conservatively, trigger conditions in the description measurably raise should-call rate. Put the same trigger in the tool description itself, not only the system prompt.
|
|
40
|
+
- Set `strict: true` (or the server's strict-schema equivalent) when the downstream call needs exact params — it constrains the model's args to the schema instead of validating after the fact.
|
|
41
|
+
- **Tool name/type pairs are load-bearing** for built-in/server tools — `text_editor_20250728` pairs with name `str_replace_based_edit_tool`; mixing a type with the wrong name 400s. Match your versioned tool `type` to its expected `name`.
|
|
42
|
+
|
|
43
|
+
5. **Make results discriminated and errors actionable.**
|
|
44
|
+
- Return a **discriminated shape** the model can branch on: a `status: "ok" | "error" | "empty"` (or typed result variants), never a bare string the model has to pattern-match.
|
|
45
|
+
- On failure, return the error **as a tool result with an error flag set** (`is_error: true` on the result block) and an **actionable message** — *"Location 'xyz' not found. Provide a valid city name."* — not an exception that kills the loop, and not a silent empty result. The model reads the message and retries or asks; a stack trace or `null` teaches it nothing.
|
|
46
|
+
- Validate inputs **inside** the tool before doing work; surface validation failures the same way so the model can correct its own args.
|
|
47
|
+
|
|
48
|
+
6. **Trim results for context efficiency — this is where most tool servers fail.**
|
|
49
|
+
- **Default to summaries.** Add a `summary`/`detail` mode and a `limit`; return aggregates + top-N, not full dumps. A tool that returns 5K rows of JSON burns thousands of tokens the model rarely needs. (Mirror of the read-tool convention: `summary=true` unless individual rows are required.)
|
|
50
|
+
- **Paginate** large lists (`cursor`/`page` + `limit`); never return an unbounded set.
|
|
51
|
+
- For **large/tabular** payloads, prefer **CSV or YAML over JSON** — same data, far fewer tokens (no repeated keys/braces). Reserve JSON for small structured results the model branches on.
|
|
52
|
+
- **Offload huge outputs to a file/handle**: if a result would exceed a large threshold (tens of K tokens), write it to a path/resource and return a **preview + the handle** so the model can fetch the slice it needs. (Hosted toolsets do this automatically past ~100K tokens — replicate the pattern.)
|
|
53
|
+
- **Programmatic / composed calls:** when the model would otherwise chain N tool calls (each result hitting its context), let it run a script that calls tools as functions and returns only the final output — intermediate results never enter context. Worth it for sequential calls or large filtered-down intermediates.
|
|
54
|
+
|
|
55
|
+
7. **Handle concurrency safely for the transport.**
|
|
56
|
+
- stdio: the host spawns **one subprocess per client** — within a process, JSON-RPC requests can interleave, so don't hold shared mutable state across `await` points without guarding it; do hold per-request state on the stack.
|
|
57
|
+
- HTTP: multiple concurrent requests share **one** process — make handlers reentrant, use connection pools (don't open a DB connection per call), and never stash request-scoped data in module globals.
|
|
58
|
+
- Mark read-only tools as parallel-safe where the framework allows; serialize anything with side effects.
|
|
59
|
+
|
|
60
|
+
8. **Add observability + rate limiting (remote especially).**
|
|
61
|
+
- Log each call's tool name, arg keys (not secret values), latency, and result size to **stderr / your logger** — never stdout on stdio.
|
|
62
|
+
- Rate-limit per-caller on remote servers; return a clear retryable error (with retry guidance) on limit, so the agent backs off instead of hammering.
|
|
63
|
+
|
|
64
|
+
9. **Test with the MCP inspector, then verify the model actually calls it right.**
|
|
65
|
+
- Run the server under the **MCP inspector** (`npx @modelcontextprotocol/inspector <server-cmd>`) — confirm tools enumerate, schemas validate, and a hand-entered call returns the expected discriminated shape and error shape.
|
|
66
|
+
- Then close the loop with a real model: give it a task that *should* trigger the tool and confirm it (a) picks the right tool, (b) fills args validly, (c) gets a result small enough to act on. A tool that passes the inspector but the model never calls (or always mis-calls) is not done — fix the **name/description**, not the prompt.
|
|
67
|
+
|
|
68
|
+
## Common Errors
|
|
69
|
+
|
|
70
|
+
- **Logging to stdout on a stdio server.** Any `print`/`console.log` corrupts the JSON-RPC stream and the client silently breaks. Everything diagnostic goes to **stderr**.
|
|
71
|
+
- **Returning raw, unbounded results.** The #1 context killer. 5K rows of pretty-printed JSON = thousands of wasted tokens. Add `summary`/`limit`/pagination and prefer CSV/YAML for tabular data **before** shipping.
|
|
72
|
+
- **Mega-tools with an `action` discriminator.** `manage_x(action=...)` makes the model get both the action and a loose schema right. Split into one tool per action.
|
|
73
|
+
- **Vague names / what-not-when descriptions.** `query`, `data`, `do` give the model nothing to route on. A description that says *what* the tool does but not *when to call it* leaves the model guessing — and newer models under-call by default, so the trigger condition has to be explicit.
|
|
74
|
+
- **Errors as exceptions or silent nulls.** An exception ends the agent loop; a `null`/empty result looks like success. Return a result with the error flag set + an actionable message so the model can recover.
|
|
75
|
+
- **API keys as tool parameters.** They land in model context and the transcript/event history. Inject credentials server-side from env/vault at egress; the model never sees them.
|
|
76
|
+
- **Mismatched versioned tool name/type pair** (built-in tools). The `type` and `name` are a fixed pair — swapping one without the other 400s.
|
|
77
|
+
- **Per-call DB connections / shared mutable state on HTTP servers.** Concurrent requests share the process; use pools and keep request state off module globals.
|
|
78
|
+
- **Schema drift the model can't see.** Changing return shape without updating the description means the model branches on a contract that no longer holds. Keep the description and the actual result shape in sync.
|
|
79
|
+
|
|
80
|
+
## Verify
|
|
81
|
+
|
|
82
|
+
- [ ] Transport chosen deliberately (stdio = local/no-auth; HTTP = remote/+auth); secrets come from env/vault, **never** from tool params.
|
|
83
|
+
- [ ] Every tool: `verb_noun` name, single responsibility, description states **when to call**, `input_schema` has a `description` on each field + `enum`/`required`/`additionalProperties:false`.
|
|
84
|
+
- [ ] Results are discriminated (`status`/typed variants); errors return an error-flagged result with an actionable message — not exceptions, not silent nulls.
|
|
85
|
+
- [ ] Large results trimmed: summary mode + pagination + limit; CSV/YAML for tabular; oversized output offloaded to a handle with a preview.
|
|
86
|
+
- [ ] Concurrency safe for the transport (no stdout logging on stdio; pooled connections + reentrant handlers on HTTP).
|
|
87
|
+
- [ ] Rate limiting + per-call logging (to stderr/logger) in place on remote servers.
|
|
88
|
+
- [ ] **Passed the MCP inspector** (tools enumerate, schemas validate, success + error shapes correct) **and** a real model picks the right tool, fills valid args, and gets a context-sized result.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-design-review
|
|
3
|
+
description: Designs and reviews HTTP/REST and RPC API surfaces — resource naming, verbs, status codes, pagination, versioning, idempotency, error envelopes, and backward compatibility. Use when creating a new endpoint or changing an existing API contract.
|
|
4
|
+
when_to_use: สร้าง endpoint ใหม่; แก้ request/response schema; เปลี่ยน API contract; ออกแบบ versioning
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Invoke before writing or merging any change that alters an API contract:
|
|
10
|
+
|
|
11
|
+
- Adding a new endpoint or RPC method.
|
|
12
|
+
- Changing a request or response body (add/remove/rename field, change type, change nullability).
|
|
13
|
+
- Changing path, query params, HTTP verb, or status codes of an existing route.
|
|
14
|
+
- Designing pagination, filtering, sorting, or a versioning strategy.
|
|
15
|
+
|
|
16
|
+
Do NOT use for: pure internal refactors with no wire-format change, or UI-only work.
|
|
17
|
+
|
|
18
|
+
## Steps
|
|
19
|
+
|
|
20
|
+
1. **Pin the contract surface.** List every consumer-visible element you touch: `METHOD /path`, query params, request body fields, response body fields, status codes, headers. If you can't enumerate it, you can't review it.
|
|
21
|
+
|
|
22
|
+
2. **Model resources as nouns, actions as verbs.** Path = pluralized noun collection (`/orders`, `/orders/{id}/items`). Never put the action in the path (`/getOrder`, `/createOrder` are wrong). Map intent to verb:
|
|
23
|
+
- `GET` read, no side effects, safe + idempotent.
|
|
24
|
+
- `POST` create / non-idempotent action → returns `201` + `Location` header (or `202` for async).
|
|
25
|
+
- `PUT` full replace, idempotent. `PATCH` partial update.
|
|
26
|
+
- `DELETE` remove, idempotent → `204` (or `200` with body).
|
|
27
|
+
- Genuinely non-CRUD action → POST a sub-resource (`POST /orders/{id}/cancel`), not a verb path.
|
|
28
|
+
|
|
29
|
+
3. **Pick the correct status code — not a blanket 200.**
|
|
30
|
+
- `201` created (with `Location`), `202` accepted-async, `204` success-no-body.
|
|
31
|
+
- `400` malformed syntax, `401` unauthenticated, `403` authenticated-but-forbidden, `404` not found, `409` conflict (e.g. duplicate, state clash), `422` well-formed but semantically invalid (validation), `429` rate-limited.
|
|
32
|
+
- `5xx` only for server faults — never for client validation failures.
|
|
33
|
+
|
|
34
|
+
4. **Standardize pagination/filter/sort across the surface.** Pick ONE pagination style and apply it everywhere: cursor-based (`?cursor=&limit=`, opaque cursor, stable for live data — preferred) or offset (`?page=&per_page=`, simpler but drifts on inserts). Return pagination metadata consistently (e.g. `{ "data": [...], "next_cursor": "...", "has_more": true }`). Filtering/sorting via explicit allow-listed params (`?status=open&sort=-created_at`); reject unknown params instead of silently ignoring.
|
|
35
|
+
|
|
36
|
+
5. **Use a single error envelope for every error response.** Same shape on every 4xx/5xx so clients parse once:
|
|
37
|
+
```json
|
|
38
|
+
{ "error": { "code": "VALIDATION_FAILED", "message": "human readable", "details": [ { "field": "email", "issue": "invalid format" } ] } }
|
|
39
|
+
```
|
|
40
|
+
`code` = stable machine string (clients branch on this, never on `message`). Don't leak stack traces / internal SQL.
|
|
41
|
+
|
|
42
|
+
6. **Require idempotency for unsafe writes.** Non-idempotent `POST` that creates resources or moves money/state must accept an `Idempotency-Key` header; server stores key→result and replays the same response on retry. `PUT`/`DELETE` must be naturally idempotent (calling twice = same end state, no error on second `DELETE` of already-gone resource → `204` or `404`, pick one and document it).
|
|
43
|
+
|
|
44
|
+
7. **Version + run a breaking-change diff.** Diff the new contract against the existing one. Backward-compatible (safe, no version bump): adding an optional field, adding a new endpoint, adding an enum value clients must tolerate, loosening validation. Breaking (needs new version or is forbidden): removing/renaming a field, changing a type, making an optional field required, tightening validation, changing status-code semantics, changing default behavior. Version via URL (`/v2/...`) or header — pick the project's existing convention. If a change is breaking, either make it additive instead, or ship under a new version and keep the old one working.
|
|
45
|
+
|
|
46
|
+
8. **Validate input at the edge.** Reject unknown/extra fields or define the policy explicitly. Enforce types, ranges, lengths, and required-ness before any business logic runs. Document every field: type, required?, default, constraints, example.
|
|
47
|
+
|
|
48
|
+
## Common Errors
|
|
49
|
+
|
|
50
|
+
- **Silent breaking change.** Renaming a response field or tightening validation looks harmless in a diff but breaks every existing client. Always diff against the deployed contract, not just the previous commit.
|
|
51
|
+
- **Status code 200 for everything** (including errors, with `{"success": false}` in the body). Clients then can't rely on HTTP semantics, retries/caching/monitoring all misbehave. Use real status codes.
|
|
52
|
+
- **`200` for async work.** If processing is deferred, return `202 Accepted` with a status/polling URL — not `200` implying it's done.
|
|
53
|
+
- **404 vs 403 leak.** Returning `404` to hide existence is sometimes intentional; returning `403` reveals the resource exists. Decide deliberately and be consistent.
|
|
54
|
+
- **POST retried twice creates duplicates** because there's no idempotency key — classic double-charge / double-order bug on flaky networks.
|
|
55
|
+
- **Offset pagination on live data** skips or repeats rows when records are inserted/deleted between pages. Prefer cursor for anything mutating.
|
|
56
|
+
- **Inconsistent error shapes** across endpoints force clients to write per-endpoint parsing. One envelope, always.
|
|
57
|
+
- **`422` vs `400` confusion.** `400` = can't even parse the request; `422` = parsed fine but the values are invalid. Mixing them makes client retry logic wrong.
|
|
58
|
+
|
|
59
|
+
## Verify
|
|
60
|
+
|
|
61
|
+
- [ ] Every path is a noun; no verbs in paths.
|
|
62
|
+
- [ ] Each operation uses the semantically correct verb and a specific status code (not blanket `200`).
|
|
63
|
+
- [ ] Error responses across the whole surface share one envelope with a stable `code` field.
|
|
64
|
+
- [ ] All create/state-changing `POST`s accept an idempotency key; `PUT`/`DELETE` are idempotent.
|
|
65
|
+
- [ ] Pagination/filter/sort follow one consistent pattern; unknown params are rejected, not ignored.
|
|
66
|
+
- [ ] Ran a contract diff vs the deployed version → any breaking change is either reworked to be additive or shipped under a new version with the old one intact.
|
|
67
|
+
- [ ] Every request/response field is documented (type, required, default, example) and validated at the edge.
|
|
68
|
+
- [ ] No internal details (stack traces, SQL, secrets) leak in any response.
|
|
69
|
+
|
|
70
|
+
If a contract test / schema snapshot exists, run it and confirm the diff matches the intended (and documented) changes only.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: async-concurrency-correctness
|
|
3
|
+
description: Writes and fixes correct async/concurrent code across Python (asyncio), TypeScript (Promises), Rust (tokio), and Go (goroutines/channels), targeting deadlocks, races, cancellation, and backpressure.
|
|
4
|
+
when_to_use: User is writing or debugging async/await, event loops, goroutines, tokio tasks, locks, channels, or hits deadlocks, races, leaked tasks, blocked event loops, or unbounded concurrency. NOT for general code tidy-ups (use refactor-cleanup).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Reach for this skill when the code touches more than one thing happening at once and correctness depends on *ordering, timing, or shared state*:
|
|
10
|
+
|
|
11
|
+
- Writing/reviewing `async`/`await`, event loops, `goroutine`+`channel`, `tokio::spawn`, thread pools, locks, queues.
|
|
12
|
+
- A symptom points at concurrency: hang/deadlock, intermittent test failure, data race, "works once then stalls", leaked/zombie tasks, OOM under load, event loop "blocked" warnings, requests timing out under concurrency but fine serially.
|
|
13
|
+
- Adding parallelism to existing serial code (fan-out, worker pool, pipeline).
|
|
14
|
+
|
|
15
|
+
Do NOT use for single-threaded refactors, naming, or non-timing-dependent logic bugs — that's `refactor-cleanup`. If a "race" reproduces 100% deterministically serially, it's a logic bug, not a concurrency bug — stop and treat it as one.
|
|
16
|
+
|
|
17
|
+
First move: **identify the runtime model before writing a line.** The right fix differs per runtime.
|
|
18
|
+
|
|
19
|
+
| Runtime | Model | Key constraint |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Python asyncio | single-thread cooperative loop | one blocking call freezes *everything*; CPU work needs a process, not a thread (GIL) |
|
|
22
|
+
| TypeScript/Node | single-thread microtask queue | sync CPU blocks the loop; `await` in a `for` serializes; unhandled rejection can crash |
|
|
23
|
+
| Rust tokio | M:N work-stealing (multi-thread) or current-thread | blocking a worker starves the pool; `!Send` futures can't cross threads |
|
|
24
|
+
| Go | M:N goroutine scheduler, preemptive | true parallelism → real data races; nil/closed channel semantics bite |
|
|
25
|
+
|
|
26
|
+
## Steps
|
|
27
|
+
|
|
28
|
+
1. **Pin the runtime + executor flavor.** asyncio (`asyncio.run`?) · tokio (`#[tokio::main]` multi-thread vs `flavor = "current_thread"`?) · Go (`GOMAXPROCS`?) · Node (worker_threads in play?). Decisions below depend on this.
|
|
29
|
+
|
|
30
|
+
2. **Get blocking work off the async path.** Anything that doesn't yield blocks the whole loop/worker:
|
|
31
|
+
- asyncio: wrap sync/CPU/file-I/O in `await asyncio.to_thread(fn, ...)`; for CPU-bound use `loop.run_in_executor(ProcessPoolExecutor(), ...)` (threads won't help under the GIL).
|
|
32
|
+
- tokio: `tokio::task::spawn_blocking(move || ...).await` for blocking syscalls/CPU; never call `std::thread::sleep`, blocking `Mutex` held across `.await`, or sync file I/O on a worker.
|
|
33
|
+
- Node: move CPU work to a `worker_thread`; never a synchronous loop, `fs.readFileSync`, or `JSON.parse` of a huge string on the main thread.
|
|
34
|
+
- Go: blocking is fine (scheduler handles it), but cap goroutines spawned around it (step 5).
|
|
35
|
+
|
|
36
|
+
3. **Never hold a sync lock across an await/yield point.** This is the #1 async deadlock.
|
|
37
|
+
- asyncio: use `asyncio.Lock` (an `async with`), not `threading.Lock`, around `await`. If you must touch a `threading.Lock`, acquire→mutate→release with *zero* awaits inside.
|
|
38
|
+
- tokio: use `tokio::sync::Mutex` only when the guard must survive `.await`. Otherwise prefer `std::sync::Mutex` and **drop the guard before** `.await` (scope it in a block, or `let v = *guard; drop(guard);`). Holding `std::sync::Mutex` across `.await` is a hang waiting to happen.
|
|
39
|
+
- Go: `sync.Mutex` is fine but never `Lock()` then `<-ch`/another `Lock()` while holding it — order all locks consistently to kill lock-ordering deadlocks.
|
|
40
|
+
|
|
41
|
+
4. **Make cancellation and shutdown explicit.** Tasks must be reliably stoppable and joined.
|
|
42
|
+
- asyncio: prefer `asyncio.TaskGroup` (3.11+) so a child failure cancels siblings and you `await` them all; use `asyncio.timeout(s)` for deadlines. Every bare `create_task` must be stored and awaited/cancelled — fire-and-forget tasks get GC'd and silently dropped. On shutdown: cancel, then `await asyncio.gather(*tasks, return_exceptions=True)`. Catch `asyncio.CancelledError`, run cleanup, then **re-raise it** — swallowing it breaks cancellation.
|
|
43
|
+
- tokio: pass a `tokio_util::sync::CancellationToken` (or `select!` on a shutdown channel) into long tasks; wrap deadlines in `tokio::time::timeout`. Note a dropped `JoinHandle` does *not* cancel the task — call `.abort()` or use a `JoinSet`. Cancellation drops at the next `.await`, so keep state consistent at every await point.
|
|
44
|
+
- Node: thread an `AbortSignal` into fetch/timers/streams; race work against `AbortSignal.timeout(ms)`.
|
|
45
|
+
- Go: every goroutine takes `ctx context.Context` and returns on `<-ctx.Done()`; pair `context.WithTimeout`/`WithCancel` with a `defer cancel()`. Use a `sync.WaitGroup` to join before exit so you don't leak.
|
|
46
|
+
|
|
47
|
+
5. **Bound concurrency — never unbounded fan-out.** Spawning one task per input will exhaust memory/sockets/fds.
|
|
48
|
+
- asyncio: gate with `asyncio.Semaphore(N)` around each unit, or process in chunks; `gather` over a *fixed* worker pool, not over N inputs.
|
|
49
|
+
- tokio: `Semaphore::new(N)` + `acquire_owned`, or `buffer_unordered(N)` on a `futures::stream`.
|
|
50
|
+
- Node: pool with `p-limit`/a concurrency cap; an unawaited `array.map(async ...)` fires all at once — wrap in `pLimit` or batch.
|
|
51
|
+
- Go: fixed-size worker pool reading from a jobs channel (`for w := 0; w < N; w++ { go worker() }`), or a buffered semaphore channel `sem := make(chan struct{}, N)`.
|
|
52
|
+
|
|
53
|
+
6. **Channels/queues must have backpressure.** Bounded by default; unbounded queues just move the OOM downstream and hide the real throughput limit.
|
|
54
|
+
- Go: `make(chan T, N)` (buffered) so producers block when consumers lag; close the channel exactly once, from the *sender* side only; never send on a closed channel. Drain or `select{ case <-ctx.Done(): }` to avoid send-blocked leaks.
|
|
55
|
+
- asyncio: `asyncio.Queue(maxsize=N)`; `await queue.put` blocks the producer under load (that's the point). Use sentinel values or `task_done()`/`join()` to signal completion.
|
|
56
|
+
- Rust: `tokio::sync::mpsc::channel(N)` (bounded), not `unbounded_channel`. `send().await` applies backpressure.
|
|
57
|
+
|
|
58
|
+
7. **Eliminate shared mutable state or make access correct.** Prefer message-passing/ownership over shared memory. If shared:
|
|
59
|
+
- Make handlers **idempotent** (retries + at-least-once delivery will re-run them).
|
|
60
|
+
- Use atomics for counters (`std::sync::atomic`, Go `atomic`, not bare `i++`); use proper guarded structures for the rest.
|
|
61
|
+
- Go: protect every shared field — a single unguarded write under `-race` is a real bug, not a warning to ignore.
|
|
62
|
+
|
|
63
|
+
8. **Verify under contention, not just once** (see Verify).
|
|
64
|
+
|
|
65
|
+
## Common Errors
|
|
66
|
+
|
|
67
|
+
- **`std::sync::Mutex` guard held across `.await` (Rust):** task parks holding the lock → deadlock. Drop the guard before awaiting, or switch to `tokio::sync::Mutex`.
|
|
68
|
+
- **`threading.Lock` instead of `asyncio.Lock` (Python):** blocks the whole loop or deadlocks. Use the async lock around awaits.
|
|
69
|
+
- **Fire-and-forget `asyncio.create_task(...)` without keeping a reference:** the task can be garbage-collected mid-flight and exceptions vanish. Store it; await or cancel it; or use `TaskGroup`.
|
|
70
|
+
- **Swallowing `CancelledError`:** `except Exception` catches it (it's `BaseException` in 3.8+ but still easy to over-catch) — clean up and re-raise, or cancellation silently no-ops.
|
|
71
|
+
- **`await` inside a `for` loop when you meant parallel (JS/Python):** serializes everything. Use `Promise.all`/`asyncio.gather` — but then add a concurrency cap (step 5) so it's not unbounded.
|
|
72
|
+
- **`Promise.all` for fan-out with no limit:** opens thousands of connections at once. Cap it.
|
|
73
|
+
- **Unhandled promise rejection (Node):** crashes or silently drops the result. Always `.catch`/`try` around detached promises; add `process.on('unhandledRejection')` as a backstop, not a fix.
|
|
74
|
+
- **`JoinHandle` dropped expecting cancellation (tokio):** dropping does NOT abort the task — it keeps running detached. Use `.abort()` or a `JoinSet`.
|
|
75
|
+
- **Closing a Go channel from the receiver, or twice:** panics. Close once, from the sole sender. Sending on a closed/nil channel panics/blocks forever.
|
|
76
|
+
- **`select` with no `default` and no ready case (Go):** blocks forever — a silent deadlock. Add a `ctx.Done()` case.
|
|
77
|
+
- **Loop variable captured by goroutine pre-Go 1.22:** all goroutines see the last value. Pass it as an arg or shadow it.
|
|
78
|
+
- **Treating `-race` / lint warnings as noise:** a reported race is a real bug. Fix the root cause; never suppress it to make CI green.
|
|
79
|
+
- **CPU-bound work in `to_thread`/`spawn_blocking` expecting parallelism (Python):** GIL serializes it. Use processes for CPU.
|
|
80
|
+
- **Blocking call sneaking onto a tokio worker** (`reqwest::blocking`, sync `std::fs`, `std::thread::sleep`): starves the runtime. Use async equivalents or `spawn_blocking`.
|
|
81
|
+
|
|
82
|
+
## Verify
|
|
83
|
+
|
|
84
|
+
A concurrency fix isn't done until it survives contention. Don't trust a single green run.
|
|
85
|
+
|
|
86
|
+
1. **Run under the race detector / sanitizer:** Go `go test -race ./...` and `go run -race`; Rust `RUSTFLAGS="-Zsanitizer=thread"` (nightly) or at minimum `cargo test` with `tokio` `--features full` and Loom for lock-free code; Python/Node — add a stress test (below).
|
|
87
|
+
2. **Stress test:** run the hot path with N concurrent callers (e.g. 100–1000), repeated 100+ iterations. Flaky pass = unfixed race. In Go run with `-count=100 -race`; in async langs spawn the op `gather`/`Promise.all`/`JoinSet` with a high count and assert invariants every run.
|
|
88
|
+
3. **Deadlock check:** add a hard timeout to the test itself (`go test -timeout 30s`, `asyncio.timeout`, jest `testTimeout`) so a hang fails loudly instead of hanging CI.
|
|
89
|
+
4. **Leak check:** assert no growth in task/goroutine count after the workload settles — Go `runtime.NumGoroutine()` before/after; asyncio `len(asyncio.all_tasks())`; tokio task count via a `JoinSet`/metrics. Steady-state count must return to baseline.
|
|
90
|
+
5. **Backpressure check:** drive producers faster than consumers and confirm bounded memory (queue depth stays ≤ N, RSS flat) instead of unbounded growth.
|
|
91
|
+
6. **Cancellation check:** trigger shutdown/timeout mid-flight and assert all tasks stopped, resources released (no leaked connections/fds/locks), and no `CancelledError`/panic escaped.
|
|
92
|
+
|
|
93
|
+
Show the actual command + output as evidence. "Looks fine" is not verification for concurrency.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audit-accessibility-wcag
|
|
3
|
+
description: Audits and fixes markup/JSX for WCAG 2.2 AA compliance — alt text, ARIA, heading order, contrast, keyboard nav, focus management; used before shipping UI or preparing an a11y review.
|
|
4
|
+
when_to_use: When the user wants an accessibility (a11y) check or fix, mentions WCAG, screen readers, ARIA, keyboard navigation, color contrast, or is preparing for an accessibility audit.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Trigger this skill when the task is to audit or fix UI for accessibility: WCAG 2.2 AA, screen readers, ARIA, keyboard nav, color contrast, focus management, or preparing an a11y review.
|
|
10
|
+
|
|
11
|
+
NOT this skill: pure visual/layout polish (no a11y intent), or security/XSS concerns — that is `security-review`. ARIA/role injection that touches `dangerouslySetInnerHTML` or user-controlled markup overlaps both; flag the a11y part here and hand the injection risk to `security-review`.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
Work in this order. Earlier rules outrank later ones — never reach for ARIA to patch a problem that semantic HTML already solves.
|
|
16
|
+
|
|
17
|
+
1. **Scope the surface.** Identify the files/components in the diff or named target. Grep for the smells that produce most findings: `rg -n 'role=|aria-|onClick=|<div|<span|tabindex|tabIndex|alt=|<img|placeholder='` across the target. Build a checklist; do not audit the whole repo unless asked.
|
|
18
|
+
|
|
19
|
+
2. **Semantic HTML first.** Replace `<div onClick>` / `<span onClick>` with a real `<button type="button">`. Use `<a href>` only for navigation (has a destination), `<button>` for actions (no navigation). Use `<nav>`, `<main>`, `<header>`, `<footer>`, `<ul>/<li>`, `<table>` over `<div role="...">`. One `<main>` per page. A native element is keyboard-operable and announced for free; a styled `div` is not.
|
|
20
|
+
|
|
21
|
+
3. **Images & icons.** Every `<img>` needs `alt`. Informative image → describe its meaning (`alt="Quarterly revenue up 12%"`), not the filename. Decorative/redundant image → `alt=""` (empty, not missing — missing alt makes SRs read the URL). Icon-only buttons need an accessible name via `aria-label` or visually-hidden text; an inline SVG that conveys meaning needs `role="img"` + `aria-label`, a decorative one needs `aria-hidden="true"`.
|
|
22
|
+
|
|
23
|
+
4. **Heading hierarchy.** Exactly one `<h1>`. No skipped levels (h2 → h4 is a fail; reflows the SR outline). Headings describe structure, not font size — never pick a level for its default styling. Don't fake headings with bold `<p>`/`<div>`.
|
|
24
|
+
|
|
25
|
+
5. **ARIA only where native fails.** First rule of ARIA: don't use ARIA. Apply it only for patterns HTML can't express (tabs, comboboxes, live regions, disclosure). When you do: every `role` must carry its required states (e.g. `role="tab"` needs `aria-selected` + `aria-controls`; `aria-expanded` on every disclosure/menu trigger; `role="dialog"` needs `aria-modal="true"` + `aria-labelledby`). Never put an interactive role on a non-interactive element without also making it focusable (`tabindex="0"`) and key-handled. Remove redundant roles (`role="button"` on a `<button>`).
|
|
26
|
+
|
|
27
|
+
6. **Forms.** Every input has a programmatic label: `<label for>` / `htmlFor`, wrapping `<label>`, or `aria-label`/`aria-labelledby`. Placeholder is NOT a label (vanishes on input, low contrast). Required → `required` + visible indicator. Errors: link the message with `aria-describedby` on the field, set `aria-invalid="true"`, and announce dynamic errors via an `aria-live="polite"` (or `role="alert"` for assertive) region. Group radios/related fields in `<fieldset>` + `<legend>`.
|
|
28
|
+
|
|
29
|
+
7. **Color contrast (WCAG 2.2 AA).** Body text ≥ 4.5:1; large text (≥ 24px, or ≥ 18.66px bold) ≥ 3:1; UI components, icons, and focus indicators ≥ 3:1 against adjacent colors. Compute ratios from the actual hex values (resolve CSS vars/Tailwind tokens to real colors first). Never rely on color alone to convey state — pair it with text/icon/shape. Check disabled, hover, and dark-mode states too.
|
|
30
|
+
|
|
31
|
+
8. **Keyboard.** Everything operable by mouse must work by keyboard. Tab order follows DOM order — fix with DOM/flexbox `order` reasoning, never positive `tabindex` (1+). `tabindex="-1"` = focusable by script only; `0` = in natural order. Every interactive element needs a visible focus indicator — do NOT `outline: none` without a replacement (`:focus-visible` ring). Add a "Skip to main content" link as the first focusable element. No keyboard traps (you can Tab out of every widget). Custom widgets implement expected keys (Esc closes, Arrow keys move within tabs/menus/listbox per ARIA APG).
|
|
32
|
+
|
|
33
|
+
9. **Focus management (dynamic UI).** On modal/dialog open: move focus into it, trap Tab within it, restore focus to the trigger on close, Esc closes. On client-side route change: move focus to the new page's `<h1>` or a focusable container so SR users aren't stranded. Toasts/async results → announce via live region, don't steal focus mid-task.
|
|
34
|
+
|
|
35
|
+
10. **Report.** Output a prioritized list grouped **Blocker → Major → Minor**, each entry = file:line, the WCAG criterion (e.g. 1.1.1, 2.4.7), the concrete problem, and a copy-pasteable fix (before → after). Blocker = blocks a user from completing a task (unlabeled control, keyboard trap, no focus on modal). Major = significant barrier (contrast fail, skipped heading). Minor = friction (redundant role, missing `lang`). Apply the fixes when asked; otherwise leave them as ready-to-paste diffs.
|
|
36
|
+
|
|
37
|
+
## Common Errors
|
|
38
|
+
|
|
39
|
+
- `alt=""` vs missing `alt` — empty is intentional "decorative, skip me"; missing makes SRs announce the file path. Not interchangeable.
|
|
40
|
+
- Placeholder used as the only label. Fails 1.3.1/4.1.2 and disappears once typing starts.
|
|
41
|
+
- `outline: none` / `outline: 0` in a CSS reset with no `:focus-visible` replacement — silently removes the focus ring for keyboard users. Single most common keyboard fail.
|
|
42
|
+
- Positive `tabindex` ("1", "2"…) to "fix" order — creates a brittle parallel tab sequence ahead of everything else. Fix DOM order instead.
|
|
43
|
+
- `aria-label` on a non-interactive, non-`role`'d element (plain `<div>`/`<span>`/`<p>`) — most SRs ignore it. The name needs an element that takes a name.
|
|
44
|
+
- `role="button"` on a `<div>` without `tabindex="0"` AND `onKeyDown` for Enter/Space — looks clickable, dead to keyboard. Just use `<button>`.
|
|
45
|
+
- Click handler only on the icon `<svg>`/`<i>` inside a control — enlarge the hit target and put the handler + name on the `<button>`.
|
|
46
|
+
- `aria-hidden="true"` on a focusable element (or an ancestor of one) — hides it from SRs while it stays in the tab order: a focusable ghost. Never wrap interactive content in `aria-hidden`.
|
|
47
|
+
- Live region added to the DOM at the same time as its message — SRs only announce changes to an *already-present* region. Render the empty `aria-live` container first, then inject text.
|
|
48
|
+
- Tailwind/utility color audit done on class names — `text-gray-400 on bg-white` is not a contrast value. Resolve to hex, then compute the ratio.
|
|
49
|
+
- Trusting the linter as "done" — `eslint-plugin-jsx-a11y`/axe catch static markup issues only. They cannot see contrast against rendered backgrounds, focus order, trap behavior, or whether a name actually makes sense. Keyboard/SR behavior needs manual or automated runtime checks.
|
|
50
|
+
|
|
51
|
+
## Verify
|
|
52
|
+
|
|
53
|
+
Static (always): `rg -n 'outline:\s*(none|0)|tabindex=["\x27][1-9]|aria-hidden=["\x27]true' <target>` returns no unjustified hits. If the project has it, run `npx eslint --no-eslintrc --plugin jsx-a11y` (or the existing a11y lint task) → zero errors. Confirm exactly one `<h1>` and no skipped heading levels.
|
|
54
|
+
|
|
55
|
+
Runtime (when a browser is available): load the page, run an axe-core scan (or `lighthouse` accessibility category) and capture the score + violations. Then keyboard-walk it: Tab from the top — every interactive element is reachable, in logical order, with a *visible* focus ring; the skip link appears first; open a modal and confirm focus enters it, Tab is trapped, Esc closes, and focus returns to the trigger.
|
|
56
|
+
|
|
57
|
+
Contrast: for each flagged pair, state the computed ratio and the threshold it must clear (4.5:1 / 3:1) — a fix isn't done until the number passes.
|
|
58
|
+
|
|
59
|
+
Report is complete only when every Blocker has a concrete code fix attached (not just a description) and each finding cites its WCAG success criterion.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audit-technical-seo
|
|
3
|
+
description: Audits and fixes technical/on-page SEO — meta tags, Open Graph/Twitter cards, JSON-LD structured data, canonicals, sitemap, robots.txt; used when improving discoverability or fixing crawlability.
|
|
4
|
+
when_to_use: When the user wants SEO improvements, mentions meta tags, Open Graph, schema.org/JSON-LD structured data, sitemap, robots.txt, canonical URLs, crawlability, or indexing.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Trigger this skill when the request involves: improving search discoverability/indexing, adding or fixing `<title>`/`<meta>` tags, Open Graph or Twitter Card previews, schema.org / JSON-LD structured data, canonical URLs, `sitemap.xml`, `robots.txt`, `hreflang`, or "why isn't this page showing up / previewing right on social". No API keys, crawler accounts, or paid tools required — this is static analysis + code fixes on the project's own source.
|
|
10
|
+
|
|
11
|
+
Skip if the ask is about off-page SEO (backlinks, content strategy, keyword research) or Core Web Vitals performance — those are different jobs (use a Lighthouse/perf pass for the latter).
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
Detect the stack first, then audit head-out. Output a **missing-items report** with concrete diffs, don't just describe.
|
|
16
|
+
|
|
17
|
+
1. **Detect framework + render mode.** `grep -rl "next" package.json` etc. Branch:
|
|
18
|
+
- Next.js App Router → `export const metadata` / `generateMetadata()` in `layout.tsx`/`page.tsx`. Do NOT hand-write `<head>` tags; they get stripped.
|
|
19
|
+
- Next.js Pages Router → `next/head` `<Head>` component, or `_document.tsx` for site-wide.
|
|
20
|
+
- Plain HTML / Vite / Astro → edit `<head>` directly or the framework's head API.
|
|
21
|
+
Find existing tags: `grep -rniE "og:|twitter:|application/ld\+json|rel=.canonical|<title|name=.description" src/ app/ public/`.
|
|
22
|
+
|
|
23
|
+
2. **Title + meta description.** Every indexable page needs a unique `<title>` (aim ≤60 chars so it isn't truncated in SERP) and `<meta name="description">` (≤160 chars, written for click-through, not keyword-stuffed). Flag pages sharing the same title/description as duplicate-content risk. In Next: set `title` (string or `{ default, template }`) and `description` in the metadata object.
|
|
24
|
+
|
|
25
|
+
3. **Open Graph + Twitter Card.** Required for correct social previews:
|
|
26
|
+
- OG: `og:title`, `og:description`, `og:type` (`website`/`article`), `og:url` (absolute), `og:image` (absolute URL, 1200×630, <5MB), `og:site_name`.
|
|
27
|
+
- Twitter: `twitter:card` = `summary_large_image`, `twitter:title`, `twitter:description`, `twitter:image`.
|
|
28
|
+
In Next, use the `openGraph` and `twitter` keys (Next auto-emits the `<meta>` tags). Images must be absolute — set `metadataBase: new URL("https://example.com")` so relative image paths resolve, otherwise OG images silently 404 for crawlers.
|
|
29
|
+
|
|
30
|
+
4. **JSON-LD structured data.** Pick the type that matches the page: `Article`/`BlogPosting` (blog), `Product` + `Offer` (commerce), `BreadcrumbList` (nav trail), `FAQPage` (Q&A), `Organization`/`WebSite` (site-wide, in root layout). Emit as `<script type="application/ld+json">` with `JSON.stringify` — in React use `dangerouslySetInnerHTML`, never `{JSON.stringify(...)}` as a text child (React escapes the quotes and breaks parsing). Include all required props for the type (e.g. `Article` needs `headline`, `image`, `datePublished`, `author`). Validate the shape against schema.org before declaring done.
|
|
31
|
+
|
|
32
|
+
5. **Canonical URLs.** Each page must self-reference a single absolute canonical (`<link rel="canonical">` / Next `alternates.canonical`). Audit for conflicts: trailing-slash variants, `?utm_`/query params, `http` vs `https`, `www` vs apex all resolving to the same content without one canonical winner. Paginated/filtered list pages each canonical to themselves, not page 1.
|
|
33
|
+
|
|
34
|
+
6. **sitemap.xml + robots.txt.**
|
|
35
|
+
- `sitemap.xml`: lists only indexable, canonical, 200-status URLs (no redirects, no `noindex` pages, no admin/auth routes). Next: `app/sitemap.ts`. Verify `<loc>` URLs are absolute and match canonicals exactly.
|
|
36
|
+
- `robots.txt`: must NOT `Disallow` paths you also list in the sitemap or want indexed. Confirm it points to the sitemap (`Sitemap: https://example.com/sitemap.xml`). Check no stray `Disallow: /` left from a staging config.
|
|
37
|
+
|
|
38
|
+
7. **Heading + semantic HTML.** Exactly one `<h1>` per page, no skipped levels (h1→h3), descriptive not styling-driven. Use `<main>`, `<nav>`, `<article>`, `<header>`; `alt` on content images. This is the crawler's content map — don't leave it as `<div>` soup.
|
|
39
|
+
|
|
40
|
+
8. **hreflang (only if i18n).** For multi-locale sites, every locale variant lists reciprocal `hreflang` tags including a self-reference and `x-default`. Skip entirely for single-locale sites — wrong/partial hreflang is worse than none.
|
|
41
|
+
|
|
42
|
+
9. **Report.** Produce a table: page · issue · severity · concrete fix (with the diff). Apply the fixes, then re-audit.
|
|
43
|
+
|
|
44
|
+
## Common Errors
|
|
45
|
+
|
|
46
|
+
- **Hand-writing `<head>` in Next App Router** — React strips it on the server. Must go through the metadata API.
|
|
47
|
+
- **Relative OG/canonical image URLs without `metadataBase`** — crawlers and social scrapers can't resolve them; previews break and canonicals get ignored. Always absolute, or set `metadataBase`.
|
|
48
|
+
- **JSON-LD as a React text child** — `<script>{JSON.stringify(data)}</script>` HTML-escapes the quotes (`"`) and the data won't parse. Use `dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}`.
|
|
49
|
+
- **Sitemap listing `noindex`/redirected/non-canonical URLs** — sends mixed signals; sitemap must only contain final indexable canonicals.
|
|
50
|
+
- **`robots.txt` Disallow vs sitemap conflict** — disallowing a URL that's in the sitemap (or carries `noindex`) wastes crawl budget and confuses indexing.
|
|
51
|
+
- **Multiple or zero `<h1>`** — common with component-based layouts where a shared header and the page both render an h1, or neither does.
|
|
52
|
+
- **Duplicate title/description across pages** — usually a layout-level default never overridden per-page; treat as a real bug, not cosmetic.
|
|
53
|
+
- **`noindex` left in from staging** — grep for `noindex` / `robots: { index: false }` before shipping; a single leftover tag silently de-indexes the page.
|
|
54
|
+
|
|
55
|
+
## Verify
|
|
56
|
+
|
|
57
|
+
- **Render the head:** build + serve, then `curl -s <url> | grep -iE "og:|twitter:|canonical|<title|ld\+json"` (or DevTools → Elements). Tags must exist in the **server-rendered HTML**, not injected client-side after load — crawlers read the initial response.
|
|
58
|
+
- **Structured data:** extract each JSON-LD block and confirm it's valid JSON and includes every required field for its `@type` per schema.org (Google Rich Results Test / Schema Markup Validator for a live check).
|
|
59
|
+
- **Social preview:** scrape with the platforms' validators (Facebook Sharing Debugger, Twitter/X Card Validator) — confirm image, title, description resolve from absolute URLs.
|
|
60
|
+
- **Sitemap/robots:** fetch `/sitemap.xml` (well-formed XML, absolute `<loc>`, no 404/redirect/noindex entries) and `/robots.txt` (references sitemap, doesn't block indexable paths).
|
|
61
|
+
- **Canonicals:** each page's canonical is absolute and self-consistent; no two pages claim the same canonical unless intended.
|
|
62
|
+
- **Done = the missing-items report has zero open high-severity items** and the above checks pass against the actually-served output, not the source file.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auth-jwt-session
|
|
3
|
+
description: Implements authentication and session management (JWT issuing/verification, refresh rotation, sessions, cookies, OAuth2/OIDC flows, RBAC checks) when building or fixing how a backend logs users in and authorizes requests.
|
|
4
|
+
when_to_use: User is implementing login/logout, JWT or session handling, refresh tokens, cookie config, OAuth/OIDC, or RBAC/permission checks. This is implementation; broad vulnerability auditing of a diff is security-review.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Use when writing or fixing the code that logs a user in and authorizes their requests:
|
|
10
|
+
|
|
11
|
+
- Implementing `login` / `logout` / `whoami` endpoints or commands
|
|
12
|
+
- Issuing or verifying JWTs (access + refresh)
|
|
13
|
+
- Refresh-token rotation, revocation, denylist/allowlist
|
|
14
|
+
- Setting auth cookies (flags, scope, expiry)
|
|
15
|
+
- Wiring an OAuth2 / OIDC provider (authorization-code + PKCE)
|
|
16
|
+
- Adding RBAC / scope / permission checks to protected routes
|
|
17
|
+
|
|
18
|
+
NOT for: auditing an existing diff for vulnerabilities (use `security-review`), or pure DB/RLS work in a managed Postgres stack (use the `supabase` skill).
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
**1. Choose the session model first — write it down before coding.**
|
|
23
|
+
- Stateless JWT: no server lookup per request; cannot instantly revoke. Use short access TTL (5–15 min) to bound the blast radius.
|
|
24
|
+
- Server session (opaque ID → store): instant revocation, server round-trip per request.
|
|
25
|
+
- Default for most APIs: **access JWT (short) + refresh token (long, server-tracked)**. This is the rest of these steps.
|
|
26
|
+
|
|
27
|
+
**2. Pick the signing algorithm and lock it.**
|
|
28
|
+
- Symmetric `HS256`: one secret signs AND verifies — only for a single-service monolith.
|
|
29
|
+
- Asymmetric `RS256`/`ES256`: private key signs, public key (JWKS) verifies — required when a separate service verifies tokens.
|
|
30
|
+
- On verify, **pass an explicit allowlist** of algorithms (e.g. `algorithms: ["RS256"]`). Never let the library read `alg` from the token header.
|
|
31
|
+
- Store signing keys outside the repo: env var / secret manager / KMS. Never commit a key or check a literal secret into source.
|
|
32
|
+
|
|
33
|
+
**3. Build the access token with minimal, validated claims.**
|
|
34
|
+
- Set `exp` (short), `iat`, `iss`, `aud`, and a stable `sub` (user id). Add a `jti` if you ever need per-token revocation.
|
|
35
|
+
- Put role/scope claims in only if every verifier trusts the issuer; otherwise look up authz server-side.
|
|
36
|
+
- On verify, check signature AND `exp`, `iss`, `aud` — a valid signature on a token for another audience is still invalid here.
|
|
37
|
+
|
|
38
|
+
**4. Implement refresh rotation + revocation.**
|
|
39
|
+
- Store each refresh token (hashed, never plaintext) with `user_id`, `expires_at`, `revoked_at`, and a `family_id`.
|
|
40
|
+
- On refresh: verify it exists, is unexpired, unrevoked → issue NEW access + NEW refresh, then mark the old one revoked (rotation).
|
|
41
|
+
- **Reuse detection:** if an already-revoked refresh token is presented, revoke the entire `family_id` — that means a stolen token was replayed.
|
|
42
|
+
- On logout: revoke the refresh token (and family). Access tokens stay valid until `exp` — that's expected with stateless JWT; keep access TTL short so this window is tiny.
|
|
43
|
+
|
|
44
|
+
**5. Set cookies correctly (if browser-facing).**
|
|
45
|
+
- Refresh token in cookie: `HttpOnly; Secure; SameSite=Lax` (or `Strict` if no cross-site nav), `Path=/auth/refresh` to limit where it's sent.
|
|
46
|
+
- Never put a token in `localStorage` if XSS is in scope — `HttpOnly` cookie is the point.
|
|
47
|
+
- `SameSite=None` REQUIRES `Secure` and only for genuine cross-site needs; pair with CSRF protection.
|
|
48
|
+
|
|
49
|
+
**6. OAuth2 / OIDC: authorization-code + PKCE only.**
|
|
50
|
+
- Generate `code_verifier` (random) → `code_challenge = S256(verifier)`. Send challenge on the authorize request; send verifier on the token exchange.
|
|
51
|
+
- Generate a random `state`, store it tied to the session, and verify it on callback (CSRF defense).
|
|
52
|
+
- For OIDC, generate a `nonce`, send it, and verify it in the returned ID token.
|
|
53
|
+
- Validate the ID token: signature via the provider's JWKS, plus `iss`, `aud`, `exp`, `nonce`. Never trust the `userinfo` response without validating the token first.
|
|
54
|
+
- Do not use the implicit flow. Do not embed a client secret in a public/CLI/SPA client — use PKCE without a secret.
|
|
55
|
+
|
|
56
|
+
**7. Authorize every protected route — deny by default.**
|
|
57
|
+
- Centralize: one middleware/guard that runs on all protected routes and returns 401/403 unless an explicit check passes. No route is public unless explicitly marked.
|
|
58
|
+
- Check the specific permission/scope for the action, not just "is logged in." A valid token is authentication, not authorization.
|
|
59
|
+
- Re-check ownership for resource access (does THIS user own THIS object), not only role.
|
|
60
|
+
|
|
61
|
+
## Common Errors
|
|
62
|
+
|
|
63
|
+
- **`alg: none` / algorithm confusion** — verifier trusts the token's own `alg`. An attacker sends `alg: none` (no signature) or swaps `RS256→HS256` and signs with the public key as an HMAC secret. Fix: hardcode the accepted algorithm list on verify.
|
|
64
|
+
- **Verifying signature but not `exp`/`aud`/`iss`** — many libraries verify the signature only unless you opt in to claim validation. An expired or wrong-audience token passes. Explicitly require these claims.
|
|
65
|
+
- **Refresh rotation without reuse detection** — you rotate tokens but don't revoke the family when an old one reappears, so a stolen token works until it naturally expires. Revoke the whole family on reuse.
|
|
66
|
+
- **Refresh tokens stored in plaintext** — a DB leak hands out live sessions. Store a hash; compare by hash.
|
|
67
|
+
- **Long-lived access tokens** — using a multi-hour access TTL "to avoid refresh complexity" means logout/ban does nothing for hours. Keep access short; put longevity in the refresh path.
|
|
68
|
+
- **Cookie missing `HttpOnly`/`Secure`** — token readable by JS (XSS) or sent over plaintext. Both flags on the auth cookie, always.
|
|
69
|
+
- **Same secret/key across environments** — a leaked staging key forges prod tokens. Separate keys per environment.
|
|
70
|
+
- **Timing-unsafe token compare** — comparing opaque tokens/secrets with `==` leaks length/content via timing. Use a constant-time compare.
|
|
71
|
+
- **Logging tokens** — access/refresh tokens or `Authorization` headers in request logs. Redact before logging.
|
|
72
|
+
- **Authn mistaken for authz** — "user is logged in" used as the only gate; any logged-in user reaches admin routes. Check the specific scope/role/ownership.
|
|
73
|
+
|
|
74
|
+
## Verify
|
|
75
|
+
|
|
76
|
+
Write tests that prove rejection, not just the happy path:
|
|
77
|
+
|
|
78
|
+
1. **Tampered token** — flip one byte of the signature → verify returns 401, never the user.
|
|
79
|
+
2. **`alg: none`** — craft an unsigned token with valid-looking claims → rejected.
|
|
80
|
+
3. **Expired token** — `exp` in the past → 401 (confirms claim validation runs, not just signature).
|
|
81
|
+
4. **Wrong audience/issuer** — valid signature, wrong `aud` → rejected.
|
|
82
|
+
5. **Refresh reuse** — use a refresh token, then present the old (rotated) one → that token AND its family are revoked; subsequent refresh fails.
|
|
83
|
+
6. **Logout** — after logout, the refresh token no longer mints access tokens.
|
|
84
|
+
7. **Authz** — a valid token lacking the required scope/role → 403 on a protected route; deny-by-default holds on an unmapped route.
|
|
85
|
+
8. **OAuth state/PKCE** — callback with mismatched `state` → rejected; token exchange with wrong `code_verifier` → fails.
|
|
86
|
+
9. **Secret scan** — `git grep` / a secrets scanner over the diff finds no literal keys or tokens; confirm logs redact `Authorization`.
|
|
87
|
+
|
|
88
|
+
Run the test suite to green before declaring done. A passing login flow with no rejection tests is not done.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brainstorm-design
|
|
3
|
+
description: Run a structured design conversation that explores requirements, proposes 2-3 approaches with tradeoffs, and converges on a validated design BEFORE any code is written — used when a feature/idea is fuzzy or under-specified.
|
|
4
|
+
when_to_use: User describes a feature/idea/problem that is vague, open-ended, or has multiple viable approaches; before write-plan or implementation; when 'should I build X or Y' framing appears. Skip for one-line trivial changes (typo, rename, log line).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
Invoke when the request is a **design decision, not a typing task**. Concrete triggers:
|
|
10
|
+
|
|
11
|
+
- The ask is a goal, not a mechanism: "add caching", "make onboarding smoother", "let users export data" — *how* is undecided.
|
|
12
|
+
- "Should I build X or Y?" / "What's the best way to do Z?" framing.
|
|
13
|
+
- The change touches ≥2 modules, a public API/schema, or a data model (decisions are expensive to reverse later).
|
|
14
|
+
- Success criteria, target users, scale, or constraints are unstated.
|
|
15
|
+
|
|
16
|
+
**Skip** (go straight to edit/write-plan) when the diff fits in one sentence: typo, rename, log line, version bump, copy tweak, adding a flag to an existing call. Burning a design round on these annoys the user.
|
|
17
|
+
|
|
18
|
+
This skill **stops at a validated design brief**. It never writes implementation code. Hand the brief to `write-plan` (or implementation) next.
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
1. **Read the surrounding code first, then ask.** Before any questions, grep the repo for the relevant module/feature so questions are informed, not generic. Skip questions the code already answers (existing patterns, current schema, conventions). Asking what you could have read yourself wastes the user's turn.
|
|
23
|
+
|
|
24
|
+
2. **Ask 3-5 narrowing questions in ONE message** (not drip-fed one at a time). Cover the axes that actually change the design:
|
|
25
|
+
- **Scope / non-goals** — what is explicitly out of scope for v1?
|
|
26
|
+
- **Users & trigger** — who hits this, how often, from where (UI, API, CLI, cron)?
|
|
27
|
+
- **Constraints** — latency budget, data volume, existing stack/deps you must reuse, deadline, backwards-compat?
|
|
28
|
+
- **Success criteria** — how do we know it works? (a measurable bar, not "it's good")
|
|
29
|
+
- **Failure tolerance** — what happens on error/empty/duplicate input — block, retry, or degrade?
|
|
30
|
+
|
|
31
|
+
Ask only the axes that are genuinely ambiguous. If the user already pinned scope, don't re-ask it.
|
|
32
|
+
|
|
33
|
+
3. **Propose 2-3 candidate approaches as a tradeoff table.** One row per approach, columns: `Approach | Effort | Risk | Reversibility | Fit`. Approaches must be *materially* different (e.g. "sync in-request" vs "background queue" vs "precompute on write") — not three flavors of the same thing. Each cell is a short concrete phrase, not a paragraph. If you can only think of one real approach, say so and skip the table — don't invent strawmen to pad to three.
|
|
34
|
+
|
|
35
|
+
4. **Recommend one** with 1-2 sentences of reasoning tied to the user's stated constraints from step 2 (e.g. "Given the 'no new infra' constraint, B avoids a queue dep while still meeting the 200ms budget"). Take a position — this is a design partner, not a menu.
|
|
36
|
+
|
|
37
|
+
5. **Probe the chosen approach for edge cases and failure modes** before locking it: empty/null input, concurrent writes, partial failure, scale ceiling, security/auth surface, migration of existing data. List the ones that genuinely apply; note how the design handles each or flag it as an open question.
|
|
38
|
+
|
|
39
|
+
6. **Emit the design brief** and stop. Exactly these sections, kept tight:
|
|
40
|
+
```
|
|
41
|
+
## Design Brief: <feature>
|
|
42
|
+
**Problem:** <1-2 sentences — the user-facing need>
|
|
43
|
+
**Chosen approach:** <name + 2-3 sentence mechanism>
|
|
44
|
+
**Why:** <the tradeoff that decided it>
|
|
45
|
+
**Non-goals:** <explicit out-of-scope for this iteration>
|
|
46
|
+
**Edge cases handled:** <bullets>
|
|
47
|
+
**Open questions:** <unresolved decisions needing user input — or "none">
|
|
48
|
+
```
|
|
49
|
+
Then hand off: "Ready for `write-plan` / implementation." Do not start coding.
|
|
50
|
+
|
|
51
|
+
## Common Errors
|
|
52
|
+
|
|
53
|
+
- **Skipping questions and jumping to a table.** If you guess the constraints, the "best" approach is best for the wrong problem. Always run step 2 unless the user already pinned every axis.
|
|
54
|
+
- **Drip-feeding questions** one per turn — exhausting and slow. Batch all of them into a single message.
|
|
55
|
+
- **Three fake approaches.** Listing minor variants of one idea to fill the table is noise. Two genuinely distinct options beat three near-duplicates.
|
|
56
|
+
- **Refusing to recommend** ("it depends, here are options"). The user invoked a design *partner*. Pick one and defend it; they can override.
|
|
57
|
+
- **Re-asking what's in the repo or in chat.** Read the code and the existing message first. Asking about conventions the codebase already demonstrates reads as not having looked.
|
|
58
|
+
- **Sliding into implementation** — pseudocode, function signatures, file edits. The deliverable is a brief, not a diff. Stop at the brief even when the design feels obvious.
|
|
59
|
+
- **Brief with no non-goals or open questions.** "Non-goals" prevents scope creep in the next phase; omitting it is the #1 cause of bloated plans downstream.
|
|
60
|
+
|
|
61
|
+
## Verify
|
|
62
|
+
|
|
63
|
+
Before declaring the design done, confirm all of:
|
|
64
|
+
|
|
65
|
+
- [ ] Read the relevant existing code/schema (not designing blind).
|
|
66
|
+
- [ ] User answered the narrowing questions, or explicitly waved them off.
|
|
67
|
+
- [ ] ≥2 materially distinct approaches were surfaced (or a clear reason only one is viable).
|
|
68
|
+
- [ ] A single approach is recommended with reasoning tied to a stated constraint.
|
|
69
|
+
- [ ] Edge cases / failure modes were enumerated and addressed or parked.
|
|
70
|
+
- [ ] Brief contains all sections including **Non-goals** and **Open questions**.
|
|
71
|
+
- [ ] **No implementation code was written** — output is the brief, handed to the next phase.
|
|
72
|
+
|
|
73
|
+
If any box is unchecked, the design is not ready to hand off — finish it before moving on.
|