Use when producing a technical design spec for a feature or Jira story. Enforces consistent spec structure covering data model, API surface, permissions, offline-sync rules where applicable, and web/mobile UI. Run before writing-plans.
Core principle: A spec says WHAT to build — not HOW to implement it line by line. Every section must be concrete enough that an implementation agent can execute without asking questions.
Announce at start: "I'm using the writing-specs skill to produce this spec."
writing-plans consumes this output)save_path := docs/superpowers/specs/<STORY-KEY>.md # story key is globally unique -> any agent derives this path with no Jira lookup, e.g. docs/superpowers/specs/CER-2037.md
branch := spec/<STORY-KEY>
pr_title := docs(spec): <STORY-KEY> <story summary>
PR merged -> post breadcrumb comment on the story # human reference only; other agents still derive save_path from the story key, not from this commentbash ${CLAUDE_PLUGIN_ROOT}/scripts/jira-site-guard.sh || exit 1
acli jira workitem comment create --key <STORY-KEY> \
--body "Spec: docs/superpowers/specs/<STORY-KEY>.md | PR: <PR_URL>"Guard: If you did not successfully fetch the Jira ticket before calling this skill, STOP and return to fetch it first. Never generate spec content from repository files alone.
Artifact encoding contract: unpadded tables, no section dropped, one-line N/A, verbatim contracts, rationale as annotation, prose < 10 lines between headings. plugins/sdlc/refs/artifact-encoding.md
# [Feature Name] — Technical Spec
**Story:** [JIRA-KEY — link or key]
**Feature:** [link to docs/features/*.md if available]
**Date:** YYYY-MM-DD
## Overview
[What this builds and why — 2 sentences max. No implementation detail.]
## Data Model
### New Entities
| Field | Type | Nullable | Default | Notes |
| --- | --- | --- | --- | --- |
| id | uuid | No | gen_random_uuid() | PK |
| ... | ... | ... | ... | ... |
Entity base class: choose the correct base class per the project's entity/ORM conventions (see project-context) — state explicitly which and why.
### Modified Entities
[Which existing entity, which fields added/removed/changed — one row per change]
### Relationships
[Text ERD — explicit cardinality]
- Organisation (1) → (many) [Entity] via organisation_id
- [Entity] (1) → (many) [ChildEntity] via [entity]\_id
## API Surface
### New Endpoints
| Method | Path | Auth/Route Type | Description |
| --- | --- | --- | --- |
| GET | /[entity] | [auth type] | List all for org |
| POST | /[entity] | [auth type] | Create |
| GET | /[entity]/:id | [auth type] | Get by ID |
| PATCH | /[entity]/:id | [auth type] | Update |
| DELETE | /[entity]/:id | [auth type] | Soft delete |
Auth/route type — state exactly one per endpoint, using the project's route/auth conventions (see project-context). Distinguish at minimum:
- authenticated, tenant/org context required
- authenticated, full user object resolved (for creates and auditable mutations)
- public, no auth (only for genuinely public endpoints)
### Request / Response Shapes
```typescript
// POST /[entity]
interface Create[Entity]Body {
field: string; // required
optionalField?: string;
}
interface [Entity]Response {
id: string;
field: string;
createdAt: string;
}
```
All TypeScript interfaces must be fully typed. No `any`.
### Permissions
| Role | List | Get | Create | Update | Delete |
| --- | --- | --- | --- | --- | --- |
| [role-1] | ✓ own org | ✓ own org | ✓ | ✓ own | ✗ |
| [role-2] | ✓ assigned | ✓ assigned | ✗ | ✗ | ✗ |
| [role-3] | ✓ org | ✓ org | ✓ | ✓ | ✓ |
Derive role/permission names from the project's permission source (see project-context) — never invent. Locate that source and enumerate the actual roles before filling this table.
## Backend Implementation
**API / infra layer:** the project's API/infra definition (see the project-context workspace→path table) — state the exact file.
**Application layer:** state the exact files this introduces, following the project's layering conventions (implementations, mappers, DTOs, or the project's equivalents), one per operation.
**Handlers / entry points:** the project's request entry points — thin, one per operation; state exact paths.
**Permissions:** the project's permission source (see project-context) — list the exact permission keys to add.
## Web UI
Omit this section if not web-scoped.
**Pages:**
| Route | File | Description |
| --- | --- | --- |
| /[domain] | src/pages/[domain]/index.tsx | List view |
| /[domain]/[id] | src/pages/[domain]/[id].tsx | Detail view |
**Components:** the project's web component location (see project-context) — list names and responsibilities.
**Data hooks:** the project's server-data fetching layer (per the web stack in project-context) — list the data hooks/queries needed, one per operation.
**State:** client state changes (per the project's web stack in project-context) — list which stores/slices change and why.
**Route permissions:** the project's route-permission config (see project-context) — which roles can access which routes.
## Mobile UI
Omit this section if not mobile-scoped.
**Screens:**
| Route | File | Description |
| --- | --- | --- |
| /(app)/[domain] | app/(app)/[domain]/index.tsx | List screen |
| /(app)/[domain]/[id] | app/(app)/[domain]/[id].tsx | Detail screen |
**Online reads:** REST via the project's API client — same endpoints as Web UI.
**Offline reads:** if the project uses offline sync (see project-context), state the read hook/location and the data it exposes. Every offline read MUST apply a tenant/org-scoped filter so a device only ever sees its own tenant's data (e.g. a JOIN or WHERE constraint on the owning tenant/org id) — describe the exact scoping filter for this entity.
**Offline writes:** if the project uses offline sync, list the offline write/transaction builders needed (one per mutating operation) and state their location per the project's sync layer (see project-context).
## Offline Sync
Omit this section entirely if the project has no offline sync — see project-context.
If the project uses offline sync, the spec MUST cover:
- **Which entities sync** — list every entity that must be available offline.
- **Scoping / tenant-isolation filter** — the rule that limits each device's synced data to its own tenant/org. State it explicitly per entity. Bucket/partition scoping in the sync config alone is often insufficient — the read path must also enforce a tenant/org-scoped filter (see Offline reads above).
- **Read hooks** — where offline reads live and what each exposes.
- **Write hooks / transaction builders** — list each one needed, what it uploads to, and which API endpoint it calls.
Define these using the project's offline-sync technology and config location (see project-context); state the exact files.
## Error Handling
| Scenario | Behaviour | HTTP Status |
| --- | --- | --- |
| [Entity] not found | Return 404 | 404 |
| Unauthorised role | Return 403 | 403 |
| Validation failure | Return 400 with field errors | 400 |
| [Domain-specific error] | [behaviour] | [status] |
**Offline behaviour:** [what happens if user submits offline write while disconnected — does it queue, fail, or show an error?]
## Out of Scope
- [Thing that sounds related but is NOT in this spec]
- [Future enhancement explicitly excluded]
## Open Questions
[Any decision still needed — each must have a suggested default so implementation can proceed]
- [ ] [Question] — Suggested default: [answer]Always derive from the project's permission source (see project-context) — never invent. Enumerate the actual roles before writing the Permissions table.
gate := [
no TBDs — every open question has a concrete answer OR a flagged decision with a suggested default,
every new endpoint has a row in the Permissions table,
every offline-synced entity has an Offline Sync section with an explicit tenant/org-scoped filter (only if the project uses offline sync),
entity relationships state exact cardinality (1:1, 1:N, M:N),
auth/route type stated for every endpoint, using the project's conventions,
all TypeScript interfaces fully typed — no `any`,
spec says WHAT to build — no HOW (no line-by-line implementation instructions),
Web / Mobile / Offline Sync sections omitted if not applicable to this story,
entity base class choice stated and justified per the project's conventions
]
before saving -> every item in gate must hold| Anti-pattern | Fix |
|---|---|
| "Add a column to the table" | "Add field x: string to [Entity] entity + generate migration" |
| Inventing a role name | Check the project's permission source first (see project-context) |
| Missing tenant/org-scoped filter in an offline read | Always enforce a tenant/org-scoped filter on every offline read |
| Editing a generated sync schema/types file in the spec | Never — it's generated; spec the sync-config/rule change instead |
any in TypeScript interfaces | Fully type every field |
| TBD without a suggested default | Add "Suggested default: X" so impl can proceed |
| Spec contains implementation code | Move to agent instructions, not spec |
| Offline Sync section absent for an offline-synced entity | Add the section — the sync implementer needs it |
8a737ca
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.