From hyperworker
Generate a Product Requirements Document (PRD) for a new feature. Use when planning a feature, starting a new project, or when asked to create a PRD. Triggers on: create a prd, write prd for, plan this feature, requirements for, spec out. Returns results only. Do not run in plan mode.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hyperworker:prdThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create detailed Product Requirements Documents that are clear, actionable, and suitable for implementation by junior developers or AI agents.
Create detailed Product Requirements Documents that are clear, actionable, and suitable for implementation by junior developers or AI agents.
Before drafting, assess the project scope to calibrate the number of tasks:
| Project scope | Example | Expected tasks |
|---|---|---|
| Single feature / bugfix | Add a priority field to tasks | 3–5 |
| Small feature set | User auth + profile page | 8–15 |
| Medium initiative | New API service with DB, auth, and UI | 15–30 |
| Large initiative | Multi-service platform migration | 30–60 |
| Major system overhaul | Full infrastructure re-architecture | 60–100+ |
Use the codebase size, number of affected systems, and integration points to pick the right band. When in doubt, aim for the upper end — more granularity doesn't hurt, but less granularity does. Tasks can always be consolidated, but vague mega-tasks produce poor agent output.
Present the estimated range to the user for confirmation before drafting.
Placeholders used below:
<branch>— today's date (YYYYMMDD) + a short kebab-case slug of the feature description (e.g.,20260415-ambient-mesh)Substitute this placeholder with its actual value everywhere it appears.
<branch> automatically from the current git branch (expected format: XXX-000-my-description). If the current git branch is main, then use today's date + a short, lowercase-kebab-case slug derived from the feature description (e.g., "Deploy Ambient Mesh" on 2026-04-15 → 20260415-ambient-mesh). Do not prompt for approval.plans/<branch>-prd.md already exists, move it to plans/archive/<branch>-prd.md before continuingplans/<branch>-prd.mdInterview the user relentlessly about every aspect of this plan until you reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one by one. For each question, provide your recommended answer.
Ask the questions one at a time using AskUserQuestion.
If a question can be answered by exploring the codebase, explore the codebase instead of asking.
After each answer (or batch of answers), update plans/<branch>-prd.md with the refined content. Continue until each branch of the design tree is resolved.
Important: Do NOT start implementing. Just create the PRD.
<branch> chosenFR-###) and unambiguousplans/<branch>-prd.md is archived to plans/archive/Before completing, use this checklist to validate all referenced artifacts and environment details exist and align. These actions must be taken.
<branch> value matches between Phase 0 and the saved plans/<branch>-prd.md filename.plans/<branch>-prd.md exists and contains the final PRD draft.plans/archive/<branch>-prd.md.[Guidance] Brief description of the feature and the problem it solves.
Build a user notifications system that delivers email notifications for important in-app events (mentions, status changes, system alerts) and lets users control which events they receive.
Today, users miss critical events because the app has no notification surface. This PRD defines the end-to-end pipeline: an API to enqueue notifications, a worker that consumes from SQS and sends via SES, a database to record delivery state, and two UI pages (inbox + preferences). The feature spans four stacks:
[Guidance] Specific, measurable objectives (bullet list).
pending / sent / failed / skipped) for auditability[Guidance] Section 3 structures the deliverables. Outcomes, tasks, objectives, or functional requirements — whatever fits the work. The example below uses Outcomes & Tasks:
- An Outcome is a one-sentence statement of what the user or system can do. No acceptance criteria.
- A Task is a substantial, self-contained unit of work with three parts:
- Description — what needs to be built and why.
- Approach — the intended implementation path: which components/files to create, which libraries or patterns to use, and the key constraints or gotchas. Describe enough that an agent understands the intended solution; leave room for judgment on exact commands and implementation details.
- Acceptance criteria — verifiable outcomes that must hold when done. Phrase them as observable states ("migration applies cleanly", "pod is Ready", "GET returns paginated rows newest-first"), not assertions on literal command output. Keep the "prove it in the real environment" expectation — but let the agent choose the means.
Do NOT script every shell invocation, pin exact command output strings, or write an "execute exactly as written, do not improvise" preamble. Treat the agent as a capable implementer following a brief, not a runbook.
Use the
<branch>-##prefix (zero-padded, two-digit) for tasks.[Guidance] Acceptance criteria must be verified in the real environment before a task is considered complete. The verification method depends on the domain:
Domain Verification Web UI Clickable in a browser — navigate to the feature, exercise the happy path and at least one edge case Kubernetes kubectl apply(or Flux reconcile) succeeds, thenkubectl get/describeconfirms the expected stateTerraform terraform planshows the expected diff,terraform applysucceeds, resource exists in the provider consoleDatabase Migration runs, connection confirmed, query returns expected results API Endpoint responds with correct status and payload (e.g., curlor integration test)If the domain is not listed, derive an equivalent: actually run the thing, observe the result, confirm it matches the acceptance criteria. Never mark a task done based solely on "the code looks right" — proof of execution is required.
Users receive email notifications for important events.
Description: As a platform engineer, I need the notification pipeline's cloud infrastructure provisioned reproducibly so the worker has a queue to consume from, an SES identity to send from, and an IAM role it can assume.
Approach: Add a new Terraform module under terraform/modules/notifications/ that provisions the SES domain identity + DKIM, a main SQS queue, a DLQ with redrive (maxReceiveCount around 5), and an IAM role assumable via IRSA by the worker's Kubernetes ServiceAccount. Expose queue URL, queue/DLQ ARNs, role ARN, and SES identity ARN as module outputs. Follow the repo's existing module layout (main.tf / variables.tf / outputs.tf / README.md) and backend conventions.
Acceptance criteria:
Description: As a backend engineer, I need persistent tables to record outbound notifications and per-user delivery preferences so the worker has somewhere to write delivery state and the API has somewhere to read inbox + preferences from.
Approach: Add a migration that creates two tables:
notifications — one row per enqueue attempt, keyed by a BIGSERIAL id, with user_id, event_type, payload (JSONB), status (one of pending / sent / failed / skipped, enforced by a CHECK), attempt_count, last_error, created_at, and sent_at.user_preferences — one row per user (user_id PK), with email_enabled (default true), event_type_optouts (TEXT[]), and updated_at.Add indexes to support the inbox query (user_id, created_at DESC) and the worker's status polling (status, created_at). Provide a clean -- Down that drops both tables in reverse order. Use the repo's existing migration runner.
Acceptance criteria:
notifications succeeds with defaults populated as expected.-- Down rolls both tables back cleanly and the migration re-applies.db/README.md documents how to run migrations against staging.Description: As a backend engineer, I need a long-running TypeScript service that consumes notification jobs from SQS, renders email content from templates, sends via SES, and persists delivery state to the database — so users actually receive notifications and we have an auditable record of every send attempt.
Approach: New service at services/notification-worker/ built on the AWS SDK v3 (client-sqs, client-ses) and pg. Long-poll SQS; each message body is { notificationId: number } (the DB row id, for idempotency). For each message: load the notification row, render the template for its event_type from src/templates/, send via SES, and update the row — sent on success; failed with an incremented attempt_count and populated last_error on failure, then rethrow so SQS redrives. Only delete the SQS message on success. Containerize with a Dockerfile. Cover success, missing-row, and SES-failure paths in unit tests with mocked AWS clients.
Acceptance criteria:
dist/.status='sent' and a logged success within ~30s.status='failed' with a populated last_error, and the SQS message is redriven after the visibility timeout.Description: As a platform engineer, I need the notification-worker running in the staging cluster behind Flux so it consumes jobs from SQS reproducibly, with declarative config in Git and no manual kubectl apply drift.
Approach: Add a Helm chart at charts/notification-worker/ and a Flux HelmRelease under clusters/base/notification-worker/ (namespace, HelmRepository, HelmRelease, IRSA-annotated ServiceAccount, Kustomization). Staging-specific values (image tag, SQS URL, region, DATABASE_URL from a Secret) live under clusters/staging/notification-worker/. Wire a Flux Kustomization in flux/staging/notification-worker.yaml. Honor the Flux suspend → apply → validate → resume protocol — suspend before any cluster change, apply manually, validate, then resume. Record cluster actions in tasks/progress.txt per the hyperworker Kubernetes protocol.
Acceptance criteria:
notifications namespace on staging.status='sent' within ~60s, visible in pod logs and the DB.Ready=True with no drift.tasks/progress.txt.Description: As a backend engineer, I need HTTP endpoints that let clients enqueue notifications and read a user's inbox — so the frontend can trigger sends and render a user's notification history.
Approach: Add apps/api/src/routes/notifications.ts with two endpoints:
POST /notifications — Zod-validated body (userId, eventType, payload), insert a pending row, enqueue an SQS message ({ notificationId }), return 201 with the new id.GET /notifications — authenticated, cursor-paginated (last-seen id), newest-first, returns { notifications, nextCursor }.Wire into the existing router. Cover the round-trip (POST → row → SQS message), cursor pagination correctness, and auth rejection in integration tests against the ephemeral test DB (docker-compose.test.yml).
Acceptance criteria:
POST /notifications inserts a row, enqueues the message, and returns 201 with { id }.GET /notifications returns items newest-first, respects limit, and pages correctly via cursor until nextCursor is null.apps/api/README.md documents the endpoints with example requests.Description: As a frontend engineer, I need a web page where users view their notifications in reverse chronological order with infinite scroll — so users have a visible surface for the notifications the system is already delivering.
Approach: Add a Next.js app-router page at apps/web/src/pages/inbox/page.tsx backed by a useNotifications React Query hook that calls GET /notifications. Render event type, payload summary, relative timestamp, and a read/unread indicator. Use the existing design tokens under apps/web/src/styles/ — no new CSS frameworks. Implement infinite scroll via the returned nextCursor. Cover the happy path in a Playwright e2e test.
Acceptance criteria:
Users control which notifications they receive.
Description: As a backend engineer, I need endpoints to read and update a user's notification preferences AND the worker to honor those preferences at send time — so users' opt-outs actually prevent sends, not just persist to the DB.
Approach: Add apps/api/src/routes/preferences.ts:
GET /preferences — return the user's row, or defaults (email_enabled: true, event_type_optouts: []) when absent; do not insert on read.PUT /preferences — Zod-validated partial body, UPSERT, return the updated row.Update the worker handler to consult preferences before calling SES: if email_enabled === false, or the notification's event_type is in event_type_optouts, mark the row status='skipped' and return without sending. A missing user_preferences row defaults to all-enabled.
Acceptance criteria:
GET /preferences returns defaults for a user with no row and does not create one on read.PUT /preferences upserts and a subsequent GET reflects the change.email_enabled=false, an enqueued notification ends up status='skipped' and SES send stats show no new send from the identity for that window.Description: As a frontend engineer, I need a web page where users toggle email on/off and opt out of specific event types — so users can self-serve their notification preferences without admin intervention.
Approach: Add apps/web/src/pages/preferences/page.tsx backed by a usePreferences query and a useUpdatePreferences mutation (optimistic update, cache invalidation on settle). UI: one toggle for email_enabled, plus a toggle per event type (from a fixed EVENT_TYPES const in apps/web/src/lib/notifications.ts) bound to membership in event_type_optouts. Save button disables while in-flight and surfaces success/failure via toasts. Use existing design tokens. Cover the toggle-save-reload flow in a Playwright e2e test.
Acceptance criteria:
status='skipped'; after re-enabling, a new enqueue is status='sent'.[Guidance] Numbered
FR-#list. Be explicit and unambiguous. State invariants the system must hold, not implementation steps.
notifications table persists every enqueue attempt with columns for user, event type, payload, status, attempt count, and last error.user_preferences table stores per-user email opt-out and per-event-type opt-out lists, defaulting (when absent) to all events enabled.status='skipped' without sending when the user has opted out.maxReceiveCount=5 attempts they land in the DLQ.POST /notifications (enqueue), GET /notifications (inbox with cursor pagination), GET /preferences, and PUT /preferences.clusters/base/notification-worker/, with env-specific overlays in clusters/<env>/notification-worker/.[Guidance] What this feature will NOT include. Critical for managing scope.
services/notification-worker/src/templates/ — richer templates are a follow-up.[Guidance] Address each of these categories where relevant:
- High-level architecture overview (diagrams, if available)
- Kubernetes considerations (namespaces, service types, cluster resources)
- Application/component boundaries and responsibilities
- Reuse of existing infrastructure or services
- Security and network policies relevant to the design
notifications row (status=pending) + SQS message → worker → SES → notifications row (status=sent|failed|skipped). The DB row is the source of truth; SQS is transport.notificationId (DB row id) as the SQS message body so redelivery always targets the same row. Multiple receives update the same row; attempt_count increments.notifications namespace holds the worker Deployment + ServiceAccount. IRSA (eks.amazonaws.com/role-arn annotation) grants AWS access — no static credentials.apps/api) owns enqueue + inbox + preferences endpoints.services/notification-worker) owns template rendering, SES send, and status transitions.apps/web) owns inbox + preferences UI.terraform/modules/notifications) owns AWS resources.apps/api router, existing Next.js app router, existing React Query + design tokens.[Guidance] Address each of these categories where relevant:
- Known constraints or dependencies
- Integration points with existing systems
- Performance requirements
ApproximateNumberOfMessagesVisible > 0 is a follow-up task; out of scope here.user_preferences row is treated as all-defaults-enabled. Rows are upserted lazily on first PUT.docker compose -f docker-compose.test.yml.main with prune: true; deleted manifests will remove cluster resources after merge.[Guidance] Quantitative, outcome-based metrics. Examples: "Reduce time to complete X by 50%", "Increase conversion rate by 10%".
status='sent' within 60 seconds of enqueue, measured over a 24-hour window in stagingemail_enabled=false MUST show status='skipped' in 100% of cases (measured by a daily SQL audit)[Guidance] Questions that must be answered to finalize scope/design/rollout. These will be resolved during the Phase 2 Design Interview.
notifications rows — keep all history indefinitely, or prune after N days?Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub joseph-ravenwolfe/hyperworker --plugin hyperworker