Skip to content
Back to skills

Ddw Create Spec

ASecurity

Creates a new spec or fix-plan from an approved PRD or a fix diagnosis. Trigger: /ddw-create-spec, during DDW's PLAN phase.

  • 13 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 12, 2026
ai-agentsgovuetestingapi

Works with

  • api

Security analysis

A100/100

Scanned October 12, 2026

npx -y skills add DiluxOne/dilux-agentic-workflow --skill ddw-create-spec --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ddw Create Spec?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ddw Create Spec
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diluxone-ddw-create-spec/badge)](https://www.skillsdirectory.com/skills/diluxone-ddw-create-spec)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ddw-create-spec
description: >
  Creates a new spec or fix-plan from an approved PRD or a fix diagnosis.
  Trigger: /ddw-create-spec, during DDW's PLAN phase.
---

# Skill: /ddw-create-spec

## Description
Creates a new spec or fix-plan from an approved PRD or a fix diagnosis, or updates an existing one.

### What a spec is, and what it is not

**A spec says how the thing in the PRD gets built, and everything in it traces back to something
the PRD asked for.** Components, files, data structures, endpoints, the order of the work, split
into blocks that can each be verified on their own. Where the PRD is technology-free on purpose,
this is where the technology is chosen and the reasons are written down.

**It does not introduce requirements.** If building the design reveals something the product has to
do that the PRD never said — a rule, a limit, a behaviour — that is not a line to add here. It is a
corrective loop back to DEFINE, and the loop exists precisely so that requirements keep coming from
one place. A requirement smuggled into a spec is a requirement no acceptance criterion covers, and
VERIFY checks the criteria.

It is binding, unlike an ADR: what it says is what CODE implements, and the blocks it declares are
what the implementation is judged against.

## Inputs
- The PRD at `docs/ddw/prd/prd-{ticket}.md` (for FEATURE, if `gates.define` is true)
- The ticket info in `.ddw-state.json` (for FIX)
- The `tier` from `.ddw-state.json`
- The project's architecture conventions (`AGENTS.md`)

## Execution Protocol

### Creation mode — FEATURE (full spec)
1. Read the complete PRD.
2. Read the architecture conventions.
3. Extract tasks by category: schema/model, business logic, endpoints/API, views/UI, testing,
   scaffolding.
4. Produce the document: header, summary, numbered blocks with atomic tasks, execution order, final
   verification.
5. Every block carries: files to create/modify, logic, required tests, completion criterion.

### Creation mode — FIX (lightweight fix-plan)
1. Read the diagnosis of the problem.
2. Identify the root cause and the affected files.
3. Produce the document: problem, root cause, solution with steps, tests, regression risk.

The **Rollback plan** is mandatory for FIX (F-SPEC-15), and the fix-plan references the RCA that
DEFINE produced (`docs/ddw/specs/rca-{ticket}.md`) — the reference is a convention of the tier, not
an F-SPEC rule. If
reverting really is trivial, the section still has to exist and say so — QUICK-FIX is the tier for
changes too small to deserve one, and it has no PLAN phase at all.

### Update mode (existing spec)
1. Read the existing spec and the PRD.
2. Identify already-completed tasks (`[x]`).
3. Identify new requirements not yet covered.
4. Append new blocks at the end with sequential numbering.
5. Preserve completed blocks — never modify them.
6. Increment `Spec loops` in the header — and `Loops since last human decision` too, unless this
   round is applying an answer a human just gave, in which case that one resets to 0 and their
   answer is recorded in the spec.

## Spec Template (FEATURE) — canonical

```markdown
# Spec {ticket}: [Title]

| Field | Value |
|-------|-------|
| Ticket | [ticket] |
| PRD | docs/ddw/prd/prd-{ticket}.md |
| Tier | FEATURE |
| Date | [timestamp] |
| Spec loops | 0 |
| Loops since last human decision | 0 |

## Summary
[The technical approach in 3–5 lines: what gets built and how.]

## Coverage: PRD → blocks
| Requirement | Covered by |
|---|---|
| FR-01 | Block 1 |
| FR-02 | Block 2, Block 3 |
| NFR-01 | Strategy: [how it is met] |

## Dependencies between blocks
[Which block depends on which, and the execution order. "None" if they are independent.]

## Block 1 — [name]

**Files**
- `path/to/file.ts` (new) — [what it does]
- `path/to/other.ts` (modified) — [what changes]

**Logic**
[What this block implements.]

**API contract** *(only if it creates/modifies an endpoint)*
- Method + path: `POST /api/...`
- Request: fields with types
- Response: fields with types
- Error codes: [list]
- Auth: [which authentication/authorization applies]

**Data model** *(only if it creates/modifies a schema — or reuses one)*
- Entity, fields with types, constraints (nullable, unique, FK, default), indexes.
- A block that only REUSES a schema an earlier block declared still fills this section, restating
  the constraints by name: *"Reuses `ticket` from Block 2 — no schema change; constraints as
  declared there: NOT NULL, default, index on estado."* The validator reads each block alone, so
  "same table as above" names no constraint and fails F-SPEC-08. Measured three times, by two
  different models, in the same place: this sentence is the shape that passes.

**Input validation** *(only if it accepts input)*
- Type, maximum length, format, allowed values.

**Error handling**
- [Which errors can occur, and how each is handled.]
- [If this block has none of its own, say exactly that in ONE bullet — "Sin condiciones de error
  propias: …" / "None — …" — and nothing else. Do not borrow an error from another block to fill
  the section. A block that takes input or touches a schema may not declare this: data that
  arrives can arrive wrong.]

**Required tests**
- [ ] [test name] — validates AC-xx
- [ ] [sad path test] — invalid input

**Completion criterion**
[Verifiable, e.g. "tests X and Y pass and the endpoint returns 201 with the created id".]

## Block 2 — [name]
[Same structure.]

## Final verification
[What has to hold once every block is done.]
```

### The spec, in the shape the validator reads

The template above is a skeleton, and a skeleton cannot be validated: every bracketed row is a
placeholder the validator drops on purpose, so an unfilled document fails and should. This is the
same document filled in — one block, complete — and it is what `validate_spec.py --tier FEATURE`
is run against. It exists because the first live FEATURE run wrote a spec from the skeleton alone
and was refused by F-SPEC-07, F-SPEC-09 and F-SPEC-16, and the model's way out was reading the
validator's source to learn the shape — knowledge that belongs here. Three couplings are
load-bearing:

- **F-SPEC-07** looks inside `API contract` for all five items — method+path, request, response,
  error codes, auth. An endpoint block missing any one of them is refused by name.
- **F-SPEC-09**: a block whose surface takes input (a form, a payload, params) MUST carry
  `Input validation` — and may not declare "no error conditions of its own", because data that
  arrives can arrive wrong.
- **F-SPEC-16** COUNTS, per block: at least as many sad-path tests as `Error handling` bullets. A
  test counts as sad-path when its line names the failure — invalid/inválido, missing/faltante,
  rejected/rechazado, duplicate/duplicado, unauthorized/no autorizado, forbidden, timeout,
  conflict, error — in either language. Two error bullets with one sad-path test is a refusal,
  however good that test is.

```markdown
# Spec {ticket}: Alta pública de tickets

| Field | Value |
|-------|-------|
| Ticket | {ticket} |
| PRD | docs/ddw/prd/prd-{ticket}.md |
| Tier | FEATURE |
| Date | 2026-08-24 |
| Spec loops | 0 |
| Loops since last human decision | 0 |

## Summary
Un endpoint público recibe el alta de un ticket, la valida y la persiste. Un solo bloque: el
formulario y su ruta.

## Coverage: PRD → blocks
| Requirement | Covered by |
|---|---|
| FR-01 | Block 1 |
| NFR-01 | Strategy: render server-side sin bundle JS, p95 medido bajo 3 s |

## Dependencies between blocks
None — Block 1 stands alone.

## Block 1 — Formulario público de alta

**Files**
- `app/routes/public.py` (new) — las rutas del formulario

**Logic**
Implements FR-01: the anonymous form posts a ticket and receives its id.

**API contract**
- Method + path: `POST /tickets`
- Request: titulo (str), email (str)
- Response: id (int), estado (str)
- Error codes: 400, 422
- Auth: public, no authentication

**Data model**
- Entity ticket: id (PK), titulo (not null), estado (default "Pendiente"), index on estado.

**Input validation**
- titulo: str, max 200, required.
- email: valid address format, max 254, required.

**Error handling**
- titulo ausente — 400 naming the field; nothing persisted.
- email malformado — 422; nothing persisted.

**Required tests**
- [ ] test_alta_devuelve_id — validates AC-01
- [ ] test_titulo_faltante_400 — the missing field is refused (sad path)
- [ ] test_email_invalido_422 — the malformed email is refused (sad path)

**Completion criterion**
The three tests pass and `POST /tickets` returns 201 with the created id.

## Block 3 — [a block that reuses Block 2's schema]
**Files**
- `app/routes/listing.py` (new) — el listado protegido

**Logic**
Implements FR-02: the listing reads the tickets Block 2 persists.

**Data model**
- Reuses `ticket` from Block 2 — no schema change; constraints as declared there: NOT NULL,
  default "Pendiente", index on estado.

**Input validation**
- estado (query): one of the fixed states, optional.

**Error handling**
- estado desconocido — 422; the list is not computed.

**Required tests**
- [ ] test_listado_por_estado — validates AC-02
- [ ] test_estado_invalido_422 — the unknown state is refused (sad path)

**Completion criterion**
Both tests pass and the listing filters by estado.

## Final verification
`validate_spec.py --tier FEATURE` passes on this document and the endpoint answers as contracted.
```

Name every sad-path test with the failure word its error bullet uses — `faltante`, `inválido`,
`rechazado` (or `missing`, `invalid`, `rejected`) — because F-SPEC-16 pairs errors to tests by
those words, and a test called `test_post_sin_email` names none of them. Block 3 above is not
decoration: the block that reuses an earlier schema is where two different models failed
F-SPEC-08 three times, and its Data model sentence is the accepted shape, verbatim.

## Fix-Plan Template (FIX) — canonical

```markdown
# Fix-plan {ticket}: [Title]

| Field | Value |
|-------|-------|
| Ticket | [ticket] |
| Tier | FIX |
| RCA | docs/ddw/specs/rca-{ticket}.md [FIX only] |
| Date | [timestamp] |
| Spec loops | 0 |
| Loops since last human decision | 0 |

## Problem
[What is failing, and how it manifests.]

## Root cause
[The technical cause, not the symptom.]

## Solution — steps
1. `path/to/file.ts:NN` — [what changes]
2. ...

## Dependencies between steps
[Order, if it matters. "None" if the steps are independent.]

## Error handling
- [error condition] — [the code, the message, and what the caller does about it]

## Tests
- [ ] **Regression test** — reproduces the original bug: fails BEFORE the fix, passes AFTER.
- [ ] [one test per error condition above, naming it — F-SPEC-16 counts them]

## Regression risk
[Low/Medium/High + what could break.]

## Rollback plan *(mandatory)*
- Steps: [how to revert] — or "trivial: revert the commit", stated explicitly
- Indicators: [what tells you to apply it]
```

> The `FR-`/`NFR-`/`AC-` identifiers and these section names are what `ddw-validate-spec` matches on
> (rules F-SPEC-01 to F-SPEC-16). Write the *content* in the user's language, but keep the
> identifiers and the structure as written here.

### The fix-plan, in the shape the validator reads

The template above is a skeleton, and a skeleton cannot be validated: every bracketed row is a
placeholder the validator drops on purpose, so an unfilled document fails and should. This is the
same document filled in, and it is what `validate_spec.py --tier FIX` is run against — errors as a
list because F-SPEC-16 counts them against the tests that name them, and prose in the error section
counts as none.

```markdown
# Fix-plan {ticket}: The config loader eats the trailing newline

| Field | Value |
|-------|-------|
| Ticket | {ticket} |
| Tier | FIX |
| RCA | docs/ddw/specs/rca-{ticket}.md |
| Date | 2026-08-05 |
| Spec loops | 0 |
| Loops since last human decision | 0 |

## Problem
`loadConfig()` returns the file without its final newline, so every rewrite of the config produces a
whole-file diff and three reviewers have now asked why.

## Root cause
`readText().rstrip()` was written to strip a BOM and takes the trailing newline with it.

## Solution — steps
1. `src/config/loader.ts:41` — strip the BOM only, with `replace(/^/, "")`.
2. `src/config/loader.ts:58` — write back the text that was read, unmodified.

## Dependencies between steps
None; the two steps are independent.

## Error handling
- The file cannot be read — `ConfigUnreadable` naming the path; the caller falls back to defaults
  and logs once.
- The file is not valid UTF-8 — the same error, with the byte offset; the fallback is the same.

## Tests
- [ ] **Regression test** — a config with a BOM keeps its trailing newline: fails BEFORE the fix,
      passes AFTER.
- [ ] An unreadable file surfaces the `ConfigUnreadable` error, and the defaults are used.
- [ ] A file with an invalid UTF-8 byte reports the offset and falls back the same way.

## Regression risk
Low — the only caller is the config writer, and its output is asserted byte for byte.

## Rollback plan *(mandatory)*
- Steps: trivial: revert the commit
- Indicators: config rewrites start showing whole-file diffs again
```

## Granularity Rules
- At most ~200 lines of code per task.
- 2+ concrete, verifiable checkboxes per task.
- List EVERY file that gets created/modified.
- Explicit dependencies between tasks and blocks.

## Output Format
```
┌─────────────────────────────────────────────────────────┐
│  /ddw-create-spec — [Created | Updated]                  │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  File: [path]                                            │
│  Type: [spec | fix-plan]                                 │
│  Blocks: [N] (N new)                                     │
│  Tasks: [N]                                              │
│  Checkboxes: [N]                                         │
│  Spec loops: [N] (since last human decision: [N])        │
│                                                          │
│  Next: review and confirm to move on                     │
└─────────────────────────────────────────────────────────┘
```

## Updating .ddw-state.json
- `gates.spec` → `true` once the user approves the spec. The path is derived from the ticket:
  `docs/ddw/specs/spec-{ticket}.md` or `docs/ddw/specs/fix-{ticket}.md`.
- `block` → `"1/N"` where N is the number of blocks (FEATURE only, indicating the current block).
  `null` for a fix-plan (FIX have no blocks).

## Language

Write the spec's content in the language the user is working in, keeping the identifiers and section
names above.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.