ddd is a skill for AI coding agents. You ask for what you want, as you already do. The agent writes it down in plain-language markdown docs first, then writes the code to match. The docs become a readable record of how the app works and how it changed.
The idea
AI writes the code now, and most of us no longer read it. What still matters is what the app does, and that's best said in plain language.
So ddd makes the docs the source of truth. You keep asking the agent for what you want; it updates the docs first, then the code. You don't have to write or edit the docs, but you can read them to follow along, and so can anyone on your team.
Structure
Everything lives in docs/. Each folder answers one question, so there's always one obvious place to look. Docs link to each other with [[wiki-links]], and frontmatter records what each doc depends on. Everything outside plan/ is built and live; everything inside it isn't built yet. Pick a file to read it.
--- type: app --- # Dinner club Hosts plan dinners, invite friends, and see who's coming. ## Who uses it - Host: creates dinners, invites people, sees the guest list - Guest: replies to an invite, sees who else is coming ## What it does - [[features/create-dinner]] - [[features/rsvp]] - [[features/reminders]] ## Glossary - Dinner: one event with a date, a place and a seat limit - RSVP: a guest's reply: going, maybe or can't go
--- type: roadmap --- # Roadmap ## Phase 1: Core Goal: a host can create a dinner and guests can reply. - [x] [[features/create-dinner]] - [x] [[features/rsvp]] - [ ] [[plan/reminders]] - [ ] Set up a sending domain for email ## Phase 2: Bigger dinners Goal: plus-ones and a firm headcount. - [ ] [[plan/plus-ones]] - [ ] [[plan/close-replies-early]]
--- type: data --- # RSVP A guest's reply to one dinner invite. | Field | Meaning | |--------|------------------------------| | guest | [[data/person]] who was invited | | dinner | [[data/dinner]] they replied to | | answer | going, maybe or can't go | | note | optional message to the host | ## Rules - One RSVP per guest per dinner. A new reply replaces the old one.
--- type: feature uses: [data/rsvp, data/dinner, integrations/email] shown-on: [pages/dinner] --- # Reply to an invite A guest opens their invite and says whether they're coming. ## Who - Anyone with the invite link. - Guests don't need an account. They give their name and email once. ## Rules - Replies close when the dinner starts. - Each invite is for one person. - If the dinner is full, new "going" replies join a waitlist. ## After - Host gets [[integrations/email#new-reply]] ## Acceptance criteria - Given a dinner with seats left, when a guest replies "going", then they appear on the guest list. - Given a full dinner, when a guest replies "going", then they join the waitlist. ## Open questions - Should guests see who said maybe?
--- type: feature status: planned extends: [features/rsvp] uses: [data/rsvp] --- # Bring a plus-one Guests who are going can bring one person with them. ## Rules - Guests who are going can add one plus-one, by name. - A plus-one takes a seat. If there's room for the guest but not the plus-one, both join the waitlist. - Plus-ones show on the guest list under who brought them. ## Changes to rsvp - "Each invite is for one person" no longer applies. - An RSVP gets a plus-one field: their guest's name, if any. - [[components/guest-list]] lists plus-ones under their guest. ## Acceptance criteria - Given a guest going with a plus-one, then "seats left" drops by two. - Given one seat left, when a guest replies going with a plus-one, then both join the waitlist. ## Open questions - Can a host turn plus-ones off for a dinner?
--- type: change status: planned changes: [features/rsvp] --- # Close replies a day before the dinner Hosts need a final headcount for the restaurant. ## Now - Replies close when the dinner starts. ## After - Replies close 24 hours before the dinner starts. - On the last day before closing, the dinner page says "Replies close tomorrow at 19:00". ## Acceptance criteria - Given a dinner starting in 23 hours, when a guest replies, then they see "Replies are closed."
--- type: idea --- # Photos after the dinner Guests could share photos from the evening on the dinner page. ## Open questions - Who can see them: everyone invited, or only who went? - Do photos expire after a while?
--- type: page uses: [features/rsvp, components/guest-list] --- # Dinner The page a guest lands on from an invite. ## What's on it 1. Dinner name, date, place and the host's note. 2. Reply buttons: Going, Maybe, Can't go. 3. Seats left, e.g. "4 of 10 seats left". 4. Who's coming, as a [[components/guest-list]]. 5. After the dinner starts: "Replies are closed."
--- type: integration --- # Email ## New reply - When: a guest replies or changes their reply. - To: the host. - Says: who replied, their answer and their note. ## Reminder - When: the day before the dinner. - To: guests who said going or maybe.
--- type: tech --- # Stack For whoever reads the code. Not needed to understand what the app does. - Runtime: Node 22, Hono - Data: SQLite, one file - UI: server-rendered HTML + htmx - Tests: one file per feature doc ## Mapping - data/rsvp → table rsvps - features/rsvp → src/features/rsvp.ts
| Folder | Answers |
|---|---|
app.md | What the app is, who uses it, the words it uses |
roadmap.md | The order plans get built in: phases, in priority order |
data/ | What the app remembers |
features/ | What the app does, its rules, and how to tell it works |
pages/ | What people see, top to bottom |
components/ | Pieces reused across pages |
integrations/ | What it talks to: sign-in, email, payments, APIs |
plan/ | Committed work that isn't built yet: new features, extensions, changes |
plan/ideas/ | Loose ideas, not committed and not on the roadmap |
plan/done/ | Plans that were built and merged, kept as a record |
.tech/ | Technical decisions, for whoever reads the code |
Workflow
Most changes start as a message to the agent. It finds the docs the change belongs in, updates them, explains what that means for the app, and asks before touching the code. Here the request matches something already on the roadmap, so the agent builds that plan instead of starting from scratch.
> /ddd let guests bring a plus-one ⏺ Bash(python .claude/skills/ddd/scripts/docs_status.py status) ⎿ No changes since last apply (17 docs). ⏺ This is already planned: plan/plus-ones.md (phase 2). It extends features/rsvp.md, so building it means merging its rules into rsvp.md. Updating the docs first. ⏺ Update(docs/features/rsvp.md) ⎿ 1 line changed, 2 added 15 - Replies close when the dinner starts. 16 - Each invite is for one person. 16 - Guests who are going can bring one plus-one. 17 - A plus-one takes a seat. If there's room for the 18 guest but not the plus-one, both join the waitlist. 19 - If the dinner is full, new "going" ⏺ Update(docs/data/rsvp.md) ⎿ 1 line added 13 | note | optional message to the host | 14 | plus-one | their guest's name, if any | ⏺ Plan What changes for people using the app - Guests replying "going" can add a plus-one's name. - Plus-ones take a seat, so "seats left" counts them. - Plus-ones show on the guest list under who brought them. Also affected - pages/dinner.md and components/guest-list.md: I'll add the plus-one line to the guest list. Question - The plan leaves one open: can a host turn plus-ones off for a dinner? If that can wait, I'll build them as always allowed and keep the question on the roadmap. When it's done - plus-ones.md is merged into rsvp.md, then moves to plan/done/ as the record of the change. Code: 5 files, 2 tests added. Apply these changes to the code? ❯ 1. Yes, apply 2. No, keep the doc changes for later 3. No, undo the doc changes
This is optional, but sometimes it's the quickest way to fix a rule or reword a label. No server watches your files. At the start of every turn the agent runs a status check that compares docs/ against the last build. If you changed something, even in another editor and without mentioning it, the agent sees the diff, checks which other docs it affects, and proposes the same kind of plan.
docs/ before it lands in code. The agent turns each request in chat into a doc edit..tech/.Reviewing a change
Here is the plus-one change from the session above, as a teammate would review it. Both come from the same update. The code diff is still there for whoever wants it; the doc diff is the one anyone can read.
## Rules - Replies close when the dinner starts.- - Each invite is for one person.+ - Guests who are going can bring one plus-one.+ - A plus-one takes a seat. If there's room for the guest but not the plus-one, both join the waitlist. - If the dinner is full, new "going" replies join a waitlist. ## Acceptance criteria+ - Given a guest going with a plus-one, then "seats left" drops by two.
@@ -18,14 +18,23 @@ export async function reply(db, dinnerId, input) { const dinner = await db.dinners.get(dinnerId); if (Date.now() >= dinner.startsAt) throw new ClosedError();- const taken = await db.rsvps.count({ dinnerId, answer: 'going' });- const status = taken < dinner.seats ? 'going' : 'waitlist';+ if (input.plusOne && input.answer !== 'going') {+ throw new ValidationError('plus_one_requires_going');+ }+ const rows = await db.rsvps.list({ dinnerId, answer: 'going' });+ const taken = rows.reduce((n, r) => n + (r.plusOne ? 2 : 1), 0);+ const need = input.plusOne ? 2 : 1;+ const status = taken + need <= dinner.seats ? 'going' : 'waitlist'; await db.rsvps.upsert({ dinnerId, guestId: input.guestId, answer: status, note: input.note ?? null,+ plusOne: input.plusOne?.trim() || null, });
Planning
The docs can describe what the app will do, not only what it does today. Describe everything you have in mind and the agent writes it up as plans in plan/, grouped into phases, without building anything. Then it works through them one at a time.
| Stage | Where it lives |
|---|---|
| Idea | plan/ideas/, a few lines, not committed and not on the roadmap |
| Planned | plan/, with rules and acceptance criteria written, on the roadmap |
| In progress | plan/, marked status: in-progress, one at a time |
| Built | features/, data/, pages/, and the rest |
So you can tell what's built and what's coming from the folder tree, without opening anything. The order lives in roadmap.md: each phase is a checklist of plans and plain tasks, top to bottom. Phase 1 is the smallest version someone would actually use. Open to see one. Ideas aren't on it; is the list of them, and promoting one means moving it up into plan/ and adding it to the roadmap.
Each /ddd next takes the next unchecked item through the same docs → plan → code loop: mark it in progress, ask any open questions, build it and run its tests. Then its doc moves out of plan/ to where it now belongs, the roadmap item is checked off, and the agent reports and stops.
> /ddd next ⏺ Bash(python .claude/skills/ddd/scripts/docs_status.py next) ⎿ Phase 1: Core: plan/reminders docs/plan/reminders.md, status: planned ⏺ Next up is reminders: guests who said going or maybe get an email the day before. Done means its two acceptance criteria pass. One open question first: - Should guests who said maybe get the reminder too? > yes, both ⏺ Update(docs/plan/reminders.md) ⎿ status: planned → in-progress; open question resolved … plan approved, code and tests written … ⏺ Bash(npm test -- reminders) ⎿ 2 passed ⏺ Bash(git mv docs/plan/reminders.md docs/features/) ⏺ Update(docs/features/reminders.md) ⎿ status line removed: it's built ⏺ Update(docs/roadmap.md) ⎿ - [x] [[features/reminders]] ⏺ Reminders are done. Phase 1 is 3 of 4: the sending domain is the last item. Say "next" to continue.
A built feature's doc describes only what's live, so you can trust every rule in it. Work that will change it is a plan of its own in plan/, until it's built. There are two kinds, both in the file browser above:
extends: [features/rsvp], describes the new behavior, and lists what will change in the RSVP doc.changes: [features/rsvp] and spells out what's true now and what will be true after.When one is built, the agent writes it into the docs it targets, which produces the doc diff in the Review section above. The plan then moves to plan/done/ as the record of why the rule changed. If the RSVP doc changes before a plan is built, the status check flags the plan so it gets re-checked against the current rules.
A few rules keep the plan honest:
plan/ is a plan-only change: the docs update, and no code is touched.Change tracking
The skill includes a small Python script, standard library only. It keeps a hash and a copy of every doc as it was at the last build, in docs/.state.json and docs/.state/.
| Command | What it does |
|---|---|
status | Docs added, changed or removed since the last build; which other docs link to them; broken links. Edits inside plan/ are listed as plan only, and plans whose target changed are flagged to re-check |
diff <doc> | Line diff of one doc against its last built version |
mark-applied | Records the current docs as built, after the code and tests pass |
restore <doc> | Puts a doc back to its last built version |
roadmap | Progress per phase, plus anything out of sync: an item checked but still in plan/, a plan missing from the roadmap, a link to a doc that moved |
next | The item to work on: the one in progress, or the first unchecked one |
Install
With the skills CLI, from your project folder (needs Node 22.20 or later):
npx skills add fabiogiolito/dddIt asks which agents to install it for: Claude Code, Cursor, Codex and others. Add -g to install it for every project, or -a claude-code -y to skip the questions. npx skills update ddd gets the latest version.
Download ddd.skill and add it under Customize > Skills.
Copy skills/ddd/ from the repo into .claude/skills/ in a project, or ~/.claude/skills/ for all of them. Other agents can read skills/ddd/SKILL.md directly; it's plain markdown.
/ddd on its own reports what changed in the docs. /ddd plan <idea> writes plans and phases without building. /ddd next builds the next roadmap item. /ddd roadmap shows progress. Anything else after /ddd is a request: start an app or make a change.