total-dumb 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/OyakSaile/im-dump-skill/main/assets/banner.png" alt="total-dumb — the /dumb agent skill explains what your AI is doing right now and why" width="860">
2
+ <img src="https://raw.githubusercontent.com/OyakSaile/dumb/main/assets/banner.png" alt="total-dumb — the /dumb agent skill explains what your AI is doing right now and why" width="860">
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
6
  <img src="https://img.shields.io/badge/npm-total--dumb-CB3837?logo=npm&logoColor=white" alt="npm package total-dumb">
7
7
  <img src="https://img.shields.io/badge/node-%E2%89%A5%2018-5FA04E?logo=node.js&logoColor=white" alt="Node 18 or newer">
8
- <img src="https://img.shields.io/badge/dependencies-0-4FD1C5" alt="zero dependencies">
9
8
  <img src="https://img.shields.io/badge/license-MIT-A78BFA" alt="MIT license">
10
9
  </p>
11
10
 
@@ -17,44 +16,42 @@
17
16
 
18
17
  Coding agents are very good at doing the work and very bad at telling you why they are doing it. You watch a migration appear, a job get scheduled, a column get indexed, and the honest answer to "why this, now?" is somewhere in a story file you have not opened.
19
18
 
20
- `/dumb` is an [Agent Skill](https://agentskills.io) that answers that question from where you actually are. It reads the story, its epic and the code around the change, then gives you the step, the bigger picture, an analogy that maps, what a senior would watch for, and where the task resumes. In your language.
19
+ `/dumb` is an [Agent Skill](https://agentskills.io) that answers that question from where you actually are. It reads the story, its epic and the code around the change, then explains in short sentences, one idea at a time, and offers to go deeper only if you want it. In your language.
21
20
 
22
- It works in **Claude Code, Cursor, Codex and OpenCode**, installs with one command, and has zero runtime dependencies.
21
+ It works in **Claude Code, Cursor, Codex and OpenCode**, and installs with one command through a small interactive installer.
23
22
 
24
23
  ## What it looks like
25
24
 
26
- You are mid-story. The migration just landed. You type `/dumb`:
25
+ `/dumb` writes a full explainer into your repo, then keeps the chat to a few lines and a path. Depth belongs in a document you can scroll, not in a wall of chat text.
27
26
 
27
+ Excerpt from `docs/dumb/2026-09-22-retry-webhook-backoff.md`. The real file also carries the system design, every other part, common mistakes, a self-check and a glossary.
28
+
29
+ ```mermaid
30
+ flowchart LR
31
+ PSP["Payment provider"] -->|"payment.confirmed webhook"| Handler["handlePaymentWebhook"]
32
+ Handler -->|"the payload"| Order["processOrder"]
33
+ Handler -->|"row written on failure"| Retries[("webhook_retries")]
34
+ Retries -->|"rows already due"| Job["Retry job, every minute"]
35
+ Job -->|"same payload, same key"| Order
28
36
  ```
29
- 🧠 What we're doing
30
- We wrote the `webhook_retries` migration for story 5.6: the table holding every
31
- failed payment webhook and when to retry it.
32
-
33
- 🎯 Why (the bigger picture)
34
- 5.6 belongs to Epic 5 (Payments): "a confirmed payment becomes a paid order,
35
- always". 5.3 (webhook) and 5.4 (idempotency) guarantee the confirmation arrives
36
- and is not processed twice, but not that it arrives at all if the order processor
37
- is down that second. Without retry a real payment vanishes, which is why 5.7
38
- (status screen) is blocked: you cannot show "paid" for something that can
39
- disappear. AC 2 fixes the shape: 1m → 5m → 25m → 2h, 5 attempts.
40
-
41
- 🪄 Analogy
42
- A tracked parcel: if nobody answers, the carrier does not bin it; they leave a
43
- notice and come back at longer and longer intervals. After 5 attempts it goes to
44
- the depot (dead letter) and someone collects it.
45
-
46
- 👀 Senior's eye
47
- - `next_attempt_at` needs an index: the job asks "what is due?" every minute.
48
- - Retry without 5.4's idempotency = double payment; reprocessing goes down the
49
- same path.
50
-
51
- 🤔 One question
52
- Why growing intervals instead of retrying every minute?
53
-
54
- ➡️ Next step: the job that reads `next_attempt_at <= now()`.
55
- ```
56
37
 
57
- Note what is *not* there: no lecture on what a migration is. Every line names something real from your task. That is the whole design constraint.
38
+ #### Why this exists
39
+
40
+ Epic 5 promises that a confirmed payment always becomes a paid order, because a customer whose money left their account and whose order still says "pending" will charge back and stop trusting the shop. Today the provider tells us once, and `handlePaymentWebhook` processes it immediately. If the order service is down for that one second, the message is gone and nobody retries, so the payment is lost silently. Story 5.4 already made reprocessing safe to repeat, which is what makes an automatic retry possible at all.
41
+
42
+ #### handlePaymentWebhook
43
+
44
+ **In general** — A webhook is one system calling another to announce that something happened, so the receiver never has to ask. The usual alternative is polling, where you ask "is it paid yet?" on a timer, which wastes calls and still adds delay.
45
+
46
+ **In this project** — The exported function in `src/payments/webhook.ts`. The provider posts the confirmation to it, and it calls `processOrder` straight away.
47
+
48
+ **How it connects** — The provider feeds it; it feeds `processOrder`. After this story it also feeds `webhook_retries` whenever `processOrder` throws, and the retry job picks up from there.
49
+
50
+ **❌ Doing it wrong** — Catching the error and returning 200 without storing anything. The provider sees success and never sends the message again, so the payment is lost with no trace to debug.
51
+
52
+ **✅ Doing it right** — Write the payload to `webhook_retries` before returning, because that row is the only evidence the payment arrived, and the retry job has nothing to work from without it.
53
+
54
+ Every rule, number and design choice in that file carries its reason. "AC 2 defines 1m, 5m, 25m, 2h" is a failure; the reason those intervals grow is the point.
58
55
 
59
56
  ## Install
60
57
 
@@ -75,20 +72,33 @@ npx total-dumb --dry-run # show what would happen, write nothin
75
72
  Also available through the `skills` CLI:
76
73
 
77
74
  ```bash
78
- npx skills add OyakSaile/im-dump-skill
75
+ npx skills add OyakSaile/dumb
79
76
  ```
80
77
 
81
- ## Three levels
78
+ ## Four levels
79
+
80
+ Every level except `terms` writes the document. The level sets how deep the document goes and how much the chat asks you.
82
81
 
83
82
  | You type | You get |
84
83
  |---|---|
85
- | `/dumb` | the six slots above, for a developer who wants the why (~250 words) |
86
- | `/dumb zero` | for someone who has never seen this before: the analogy carries it, every technical word glossed on first use, no senior section (~300 words) |
87
- | `/dumb terms` | just the vocabulary: 3 to 8 terms from this step, each with a concrete example from your task |
84
+ | `/dumb` | the full document, then a few lines in chat and one line offering to explain a term or move on |
85
+ | `/dumb zero` | the full document with a wider glossary and an everyday analogy per part; the chat stops and asks which term to explain, plus one question to check you followed |
86
+ | `/dumb senior` | the document without glossary or analogies, with the trade-offs and the alternatives that were rejected; the chat asks nothing |
87
+ | `/dumb terms` | no file, just the vocabulary of this step in chat, each term with an example from your task |
88
+
89
+ Aliases: `eli5`, `beginner` and `junior` for `zero`; `pro` and `expert` for `senior`; `termos`, `jargon` and `glossary` for `terms`.
90
+
91
+ With no level given it reads your message. Use the step's terms correctly, or ask a sharp trade-off question, and you get the `senior` treatment. Say "I'm lost" and you get `dev`. It will not offer to define a word you just used correctly.
88
92
 
89
- Aliases: `eli5` and `beginner` for `zero`; `termos`, `jargon` and `glossary` for `terms`.
93
+ You do not have to use the slash command. "why are we doing this?", "what is this for?", "I don't get it", "explain this step" all trigger it mid-task. Ask in any language and both the document and the reply come back in it, headers included.
94
+
95
+ ## What lands in your repo
96
+
97
+ ```
98
+ docs/dumb/2026-09-22-retry-webhook-backoff.md
99
+ ```
90
100
 
91
- You do not have to use the slash command. "why are we doing this?", "what is this for?", "I don't get it", "explain this step" all trigger it mid-task. Ask in any language and the answer comes back in it, headers included.
101
+ One file per feature, rewritten when you ask again. Inside: why it exists, the big picture as a labelled Mermaid flow, the system design with the current feature highlighted and the planned connections dotted, a sequence diagram of the main path including the failure branch, then every part with what it is in general, what it is here, what it connects to, a worked example, and a wrong-way and right-way pair. It closes with the common mistakes on this feature, three questions with collapsed answers, and a glossary.
92
102
 
93
103
  ## Where the "why" comes from
94
104
 
package/bin/cli.mjs CHANGED
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { sep } from "node:path";
5
5
  import { stdin, stdout, stderr, exit } from "node:process";
6
- import { createInterface } from "node:readline/promises";
6
+ import * as p from "@clack/prompts";
7
7
  import { AGENTS, AGENT_IDS } from "../src/agents.mjs";
8
8
  import { parseArgs, USAGE } from "../src/args.mjs";
9
9
  import { detectAgents, install, normalizeAgents, resolveTargets, uninstall } from "../src/install.mjs";
@@ -43,12 +43,38 @@ let scope = opts.scope ?? "global";
43
43
 
44
44
  const interactive = Boolean(stdin.isTTY) && !opts.yes;
45
45
  if (interactive) {
46
- const rl = createInterface({ input: stdin, output: stdout });
47
- try {
48
- if (!opts.agents) agents = await askAgents(rl, detected);
49
- if (!opts.scope) scope = await askScope(rl);
50
- } finally {
51
- rl.close();
46
+ p.intro(`total-dumb ${pkg.version} — /dumb explains what we're doing and why`);
47
+ if (detected.length) {
48
+ p.log.info(`Detected: ${detected.map((id) => AGENTS[id].displayName).join(", ")}`);
49
+ } else {
50
+ p.log.warn("No agents detected on this machine. Pick where to install anyway.");
51
+ }
52
+
53
+ if (!opts.agents) {
54
+ const picked = await p.multiselect({
55
+ message: "Which agents?",
56
+ options: AGENT_IDS.map((id) => ({
57
+ value: id,
58
+ label: AGENTS[id].displayName,
59
+ hint: detected.includes(id) ? "detected" : undefined,
60
+ })),
61
+ initialValues: detected.length ? detected : AGENT_IDS,
62
+ required: true,
63
+ });
64
+ if (p.isCancel(picked)) { p.cancel("Nothing installed."); exit(0); }
65
+ agents = AGENT_IDS.filter((id) => picked.includes(id));
66
+ }
67
+
68
+ if (!opts.scope) {
69
+ const where = await p.select({
70
+ message: opts.uninstall ? "Remove from where?" : "Where?",
71
+ options: [
72
+ { value: "global", label: "Global — this machine", hint: "~/.claude/skills, ~/.cursor/skills, ~/.codex/skills, ~/.config/opencode/skills" },
73
+ { value: "project", label: "Project — this repo", hint: ".claude/skills, .agents/skills" },
74
+ ],
75
+ });
76
+ if (p.isCancel(where)) { p.cancel("Nothing installed."); exit(0); }
77
+ scope = where;
52
78
  }
53
79
  }
54
80
 
@@ -62,36 +88,24 @@ if (agents.length === 0) {
62
88
  }
63
89
 
64
90
  const targets = resolveTargets({ agents, scope });
91
+ const spinner = interactive ? p.spinner() : null;
92
+ spinner?.start(opts.uninstall ? "Removing…" : "Installing…");
65
93
  let results;
66
94
  try {
67
95
  results = opts.uninstall
68
96
  ? await uninstall(targets, { dryRun: opts.dryRun })
69
97
  : await install(targets, { dryRun: opts.dryRun });
70
98
  } catch (error) {
99
+ spinner?.stop("Failed", 1);
71
100
  stderr.write(`${error.message}\n`);
72
101
  exit(1);
73
102
  }
103
+ spinner?.stop(opts.uninstall ? "Removed" : "Installed");
74
104
 
75
105
  printSummary(results);
106
+ if (interactive) p.outro(results.some((r) => r.status === "failed") ? "Some targets failed." : "Done.");
76
107
  exit(results.some((r) => r.status === "failed") ? 1 : 0);
77
108
 
78
- async function askAgents(rl, detected) {
79
- stdout.write("\nWhich agents?\n");
80
- AGENT_IDS.forEach((id, i) => {
81
- stdout.write(` ${i + 1}) ${AGENTS[id].displayName}${detected.includes(id) ? " (detected)" : ""}\n`);
82
- });
83
- const fallback = detected.length ? detected : AGENT_IDS;
84
- const suggested = fallback.map((id) => AGENT_IDS.indexOf(id) + 1).join(",");
85
- const answer = await rl.question(`Numbers separated by commas [${suggested}]: `);
86
- const picked = answer.split(",").map((n) => AGENT_IDS[Number(n.trim()) - 1]).filter(Boolean);
87
- return picked.length ? AGENT_IDS.filter((id) => picked.includes(id)) : fallback;
88
- }
89
-
90
- async function askScope(rl) {
91
- const answer = await rl.question("Where? (g)lobal for this machine, (p)roject for this repo [g]: ");
92
- return answer.trim().toLowerCase().startsWith("p") ? "project" : "global";
93
- }
94
-
95
109
  function printSummary(results) {
96
110
  const verb = opts.uninstall ? "Removed" : "Installed";
97
111
  stdout.write(`\n${opts.dryRun ? "[dry-run] " : ""}${verb} the "dumb" skill:\n`);
@@ -102,5 +116,5 @@ function printSummary(results) {
102
116
  stdout.write(` ${icon} ${names.padEnd(26)} ${where} ${r.status}${r.error ? ` (${r.error})` : ""}\n`);
103
117
  }
104
118
  const didWrite = results.some((r) => r.status === "installed" || r.status === "updated");
105
- if (!opts.uninstall && !opts.dryRun && didWrite) stdout.write("\nMid-task, type: /dumb /dumb zero /dumb terms\n");
119
+ if (!opts.uninstall && !opts.dryRun && didWrite) stdout.write("\nMid-task, type: /dumb /dumb zero /dumb senior\n");
106
120
  }
package/package.json CHANGED
@@ -1,13 +1,45 @@
1
1
  {
2
2
  "name": "total-dumb",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Agent skill that explains what your AI is doing right now and why, with an analogy. One npx install for Claude Code, Cursor, Codex and OpenCode.",
5
5
  "type": "module",
6
- "bin": { "total-dumb": "bin/cli.mjs" },
7
- "files": ["bin", "src", "skills", "README.md"],
8
- "engines": { "node": ">=18" },
9
- "scripts": { "test": "node --test test/*.test.mjs" },
10
- "keywords": ["agent-skills", "skill", "claude-code", "cursor", "codex", "opencode", "bmad", "mentor", "learn"],
6
+ "bin": {
7
+ "total-dumb": "bin/cli.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "skills",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "test": "node --test test/*.test.mjs"
20
+ },
21
+ "keywords": [
22
+ "agent-skills",
23
+ "skill",
24
+ "claude-code",
25
+ "cursor",
26
+ "codex",
27
+ "opencode",
28
+ "bmad",
29
+ "mentor",
30
+ "learn"
31
+ ],
11
32
  "author": "Kayo Elias",
12
- "license": "MIT"
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/OyakSaile/dumb.git"
37
+ },
38
+ "homepage": "https://github.com/OyakSaile/dumb#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/OyakSaile/dumb/issues"
41
+ },
42
+ "dependencies": {
43
+ "@clack/prompts": "^1.8.1"
44
+ }
13
45
  }
@@ -1,83 +1,82 @@
1
1
  ---
2
2
  name: dumb
3
- description: Use when the user invokes /dumb or, in the middle of any task (a BMAD story, a refactor, a bug fix, a migration, a config change), asks why the current step exists or what it is for — "why are we doing this?", "what is this for?", "I don't get it", "explain this step", "não entendi", "por que isso?", "explica". Optional level after the name — dev (default), zero, terms.
3
+ description: Use when the user invokes /dumb or, in the middle of any task (a BMAD story, a refactor, a bug fix, a migration, a config change), asks why the current step exists or what it is for — "why are we doing this?", "what is this for?", "I don't get it", "explain this step", "não entendi", "por que isso?", "explica". Optional level after the name — dev (default), zero, senior, terms.
4
4
  ---
5
5
 
6
6
  # dumb — explain the current step and its why
7
7
 
8
8
  ## Overview
9
9
 
10
- The user is watching work happen and wants to understand it, not just get it done. Reply with a short explanation of what is being done right now and why it matters in the bigger picture, in the user's language, then say where the task resumes.
10
+ An agent is shipping features faster than anyone can read them, and the system design disappears into the diff. This skill exists so the person actually learns it. Write a structured Markdown explainer into the project, then keep the chat short and let the user ask for more.
11
11
 
12
- Core principle: **specific beats correct-but-generic.** Every sentence names something real from this task (the story, the file, the table, the command). "What a migration is" fails; "why this migration must land before story 5.7" passes.
12
+ **Everything has a why.** Every rule, number, acceptance criterion, pattern and design choice you mention carries its reason, in the same sentence or the next. Never "AC 2 defines 1m → 5m → 25m → 2h, 5 attempts". Instead: "AC 2 spaces the retries as 1m → 5m → 25m → 2h because the first one catches a short blip cheaply, the later ones stop hammering a service that is genuinely down, and 5 attempts cap the wait near three hours before a human has to look." If the real reason is not written down anywhere, say so and give the most likely one, labelled as a guess.
13
13
 
14
- ## Steps
14
+ **Specific beats correct-but-generic.** Every sentence names something real: the story, the file, the table, the queue.
15
15
 
16
- 1. **Pick the level and the language.** The level is the first word after `dumb` in the invocation, else a cue in the message, else `dev`. The language is the one the user writes in this conversation; when the invocation is too short to tell (`/dumb terms`), take it from the surrounding conversation and the project's docs. Every word of the answer is in that language, headers included.
16
+ **One idea at a time.** A reply the user has to decode has failed, however accurate it is.
17
17
 
18
- | Level | Aliases | Reader |
19
- |---|---|---|
20
- | `dev` (default) | — | a developer who knows the basics and wants the why |
21
- | `zero` | `eli5`, `beginner` | someone who has never seen anything like this |
22
- | `terms` | `termos`, `jargon`, `glossary` | wants the vocabulary of this step |
18
+ ## Simplicity rules — everywhere
23
19
 
24
- 2. **Locate the bigger picture.** Read at most 3 files; stop as soon as every slot of the template can be filled with something specific.
25
- - The conversation: what task is in flight and what was just done.
26
- - If `docs/stories/` exists (BMAD): the current story file, then its epic file (`docs/epics/`, `docs/epic-*.md`, `docs/prd*.md`, `docs/architecture*.md`).
27
- - Otherwise: `README*`, `docs/`, `ROADMAP*`, `docs/adr/`, `CHANGELOG*`, `PLAN*.md`, `TODO*.md`, `AGENTS.md`, `.claude/`.
28
- - The code: what calls or depends on the thing being changed.
29
- What to pull out of each source is in [references/context.md](references/context.md).
30
- Nothing found? The answer opens with one line saying so, before the first section header ("No planning docs found; explaining from the code", in the user's language), then reasons from the code.
20
+ - Short sentences, one idea each.
21
+ - At most one new technical term per sentence.
22
+ - No `term (meaning)` stacks. Definitions belong in the Glossary.
23
+ - No chains of story IDs. One story back, one story forward, in plain words.
24
+ - Analogies only where they help: everyday, 2 sentences at most, mapping one thing.
31
25
 
32
- 3. **Fill the template for the level**, in the user's language (headers included). Templates and a worked example are in [references/levels.md](references/levels.md). The `dev` template is below.
26
+ ## Steps
33
27
 
34
- 4. **Close with `➡️ Next step`** — one line naming where the task resumes. The explanation does not redo, re-run, or re-implement anything already done. If the task was already in progress and the user has not asked to pause, continue it after this line.
28
+ 1. **Pick the level and the language.** An explicit level after `dumb` wins; otherwise use step 2. The language is the one the user writes in this conversation; when the invocation is too short to tell (`/dumb terms`), take it from the surrounding conversation and the project's docs. Every word of the document and the reply is in that language, headers included.
35
29
 
36
- ## The `dev` template
30
+ | Level | Aliases | Document | Chat ends with |
31
+ |---|---|---|---|
32
+ | `dev` (default) | — | full | "read it first"; after the user has read it, one offer line |
33
+ | `zero` | `eli5`, `beginner`, `junior` | full, bigger Glossary, an analogy per part | "read it first"; after the user has read it, the check block, then stop |
34
+ | `senior` | `pro`, `expert` | no Glossary, no analogies, trade-offs in the why | nothing |
35
+ | `terms` | `termos`, `jargon`, `glossary` | none, chat only | nothing |
37
36
 
38
- ```
39
- 🧠 What we're doing
40
- <1–2 sentences. Names the story / file / function / command being touched right now.>
37
+ 2. **Adaptive default.** With no level given, read the user's message. It uses the step's technical terms correctly, or asks a precise trade-off question → `senior`. "não entendi", "I'm lost", "o que é isso", or a bare "why?" → `dev`. Never offer to define a term the user just used correctly.
41
38
 
42
- 🎯 Why (the bigger picture)
43
- <At most 4 sentences: the goal this serves; where it sits (epic, roadmap,
44
- dependency chain); what breaks or gets harder without it; why now and not later.>
39
+ 3. **Find the context, then the parts.** Search order and what to extract are in [references/context.md](references/context.md). Then list the parts this feature touches, by their real names: components, services, queues, workers, agents, tables, endpoints, jobs. Take them from the story or spec and from the code, never from imagination.
45
40
 
46
- 🪄 Analogy
47
- <2–3 sentences. One real-world analogy, each part mapped to one part of this step.>
41
+ 4. **Write the document.** If `docs/dumb/*-<feature-slug>.md` already exists, overwrite that file and keep its name, so re-running `/dumb` on the same feature updates one document instead of adding another; otherwise create `docs/dumb/<yyyy-mm-dd>-<feature-slug>.md`, creating the folder if needed. The template, the diagram rules and a worked excerpt are in [references/document.md](references/document.md). Skip this step at `terms`.
48
42
 
49
- 👀 Senior's eye
50
- <2 bullets, one sentence each: trade-offs or pitfalls a senior would watch for right here.>
43
+ 5. **Reply in chat**, at most 100 words plus the path. Shapes and dialogue rules are in [references/levels.md](references/levels.md).
51
44
 
52
- 🤔 One question
53
- <One question the user can answer to check they got it. Omit if it would be forced.>
45
+ ## The chat reply
54
46
 
55
- ➡️ Next step: <one line>
56
47
  ```
48
+ <3–5 short lines: what we're doing and why it matters, plain words.>
49
+
50
+ 📄 <path to the file that was written>
51
+ Read the whole document first. When you are done, say so and we go on from there.
52
+ ```
53
+
54
+ At `senior` the last line is omitted. The document carries the depth. The chat carries the invitation. Never paste the document into the chat, and never ask a question or offer terms in this first reply: the user has not read the document yet.
57
55
 
58
- Length: about 250 words. Cut, do not compress.
56
+ ## Dialogue
59
57
 
60
- ## When the task is a BMAD story, the "why" slot states
58
+ Questions come only after the user says they have read the document ("li", "read it", "done"). At that point the level's ending applies: `dev` sends the single offer line; `zero` sends the check block (terms to pick plus one comprehension question) and stops, even when the same message also says to continue, because at `zero` the check is the gate back to the task; `senior` continues the task with no questions. When the user picks a term, answer in chat, about 80 words: one sentence of meaning, then how it works in this project naming a real file or function, then one tiny example. A wrong answer to the check question gets a different and simpler explanation, then one new question. "entendi" / "got it" closes with `➡️ Next step` and nothing else. Never offer a term the user already knows or already had explained, and stop asking the moment they turn back to the task.
61
59
 
62
- - which epic the story belongs to and what the epic delivers;
63
- - which earlier stories it builds on;
64
- - which later stories are blocked by it;
65
- - the acceptance criterion that carries the motivation.
60
+ ## When the task is a BMAD story
66
61
 
67
- This holds at `dev` and `zero`. At `zero` the same four facts are told in the analogy's terms ("the 5.7 screen cannot start until this exists"), not dropped.
62
+ The "why" says which epic the story serves and what that epic delivers, what the most relevant earlier story already built, and the one later story that needs this one. Plain words, never ID arithmetic, and every one of them carries its reason.
68
63
 
69
64
  ## Honesty
70
65
 
71
- If the motivation is weak, unclear, or the step looks unnecessary, the "why" slot says so. Learning to judge work is part of becoming a better developer.
66
+ If the motivation is weak, unclear, or the step looks unnecessary, say so. Learning to judge work is part of becoming a better developer.
72
67
 
73
68
  ## Common mistakes
74
69
 
75
70
  | Mistake | Fix |
76
71
  |---|---|
77
- | Generic lecture ("migrations let you evolve the schema") | Name the table, the story, the story that depends on it |
78
- | Analogy that decorates instead of maps | Each part of the analogy = one part of the step; if it does not map, choose another |
79
- | Answering from the conversation alone when `docs/stories/` or a PRD exists | Read the story + its epic first |
80
- | Replying in English to a user who wrote in another language | Language comes from the conversation and the project's docs, not from the trigger message |
81
- | Reasoning from the code without saying the planning docs were missing | That line opens the answer, before the first section header |
82
- | Continuing to implement inside the explanation | The explanation ends at `➡️ Next step`; work resumes after it |
83
- | Past ~300 words at `dev` | Cut to ~250 for `dev`, ~300 for `zero`; the `🎯` slot is where the padding is |
72
+ | Stating a rule, number or acceptance criterion with no reason | Every one carries its why in the same sentence or the next |
73
+ | Inventing a why that is not in the docs or the code | Say the reason is not recorded, then give the likely one and label it a guess |
74
+ | Pasting the document into the chat | The chat is 3–5 lines plus the path |
75
+ | A part described only as it exists here | Three layers: in general with the usual alternative, in this project, how it connects |
76
+ | Stacked definitions, three `term (meaning)` pairs in a row | Name the term and move on; the Glossary defines it |
77
+ | A chain of story IDs | One story back, one story forward, in plain words |
78
+ | Asking a question or offering terms before the user has read the document | The first reply ends at "read the whole document first"; questions come after they say they read it |
79
+ | Explaining the offered terms at `zero` before the user asks | The check block offers; the user picks; then explain |
80
+ | Quizzing a user who already said they get it, or who asked to continue after the check | Stop asking the moment they turn back to the task; at `zero`, "I read it" still gets the check block once, even with "continue" in the same message |
81
+ | Offering to define a term the user just used correctly | That user is `senior`; give trade-offs instead |
82
+ | Replying in English to a user who wrote in another language | Language comes from the conversation and the project's docs |
@@ -7,7 +7,7 @@ Read at most 3 files. Stop when every template slot can be filled with something
7
7
  | Source | Extract |
8
8
  |---|---|
9
9
  | The conversation | the task in flight; the file / command just touched; what the user already knows |
10
- | BMAD story file (`docs/stories/<n>.<m>.story.md`, `docs/stories/*<n>.<m>*`) | the "so that" clause; `Depende de` / `Bloqueia` (or `depends on` / `blocks`); the acceptance criterion behind the current task; which task is checked and which is next |
10
+ | BMAD story file (`docs/stories/<n>.<m>.story.md`, `docs/stories/*<n>.<m>*`) | the "so that" clause; the single most relevant story it depends on and the single story it unblocks, to be named in plain words rather than as an ID chain; the acceptance criterion behind the current task; which task is checked and which is next |
11
11
  | BMAD epic (`docs/epics/epic-<n>*.md`, `docs/epic-<n>*.md`, `docs/prd*.md` section) | the epic's goal in one sentence; the story list and its order (what comes before and after) |
12
12
  | PRD (`docs/prd*.md`) | the product goal the epic serves |
13
13
  | Architecture doc (`docs/architecture*.md`) | the technical design or constraint this step has to respect |
@@ -0,0 +1,154 @@
1
+ # The explainer document
2
+
3
+ Write it to `docs/dumb/<yyyy-mm-dd>-<feature-slug>.md`. Create `docs/dumb/` if it does not exist. If a file matching `docs/dumb/*-<feature-slug>.md` is already there, overwrite that file and keep its name (one document per feature, updated on every run). No file is written at `terms`.
4
+
5
+ There is no length limit. The document is as long as the parts need: never shorten a part, drop a layer or skip an example to save space. A six-part feature can run past 2,000 words and that is fine. Every section is grounded in this project, with real names. The simplicity rules still apply sentence by sentence, and every rule, number and design choice carries its reason.
6
+
7
+ Headers below are shown in English, the canonical form; render them in the user's language.
8
+
9
+ ## Template
10
+
11
+ ````markdown
12
+ # <Feature or story name> — explained
13
+
14
+ ## Why this exists
15
+ <4–8 sentences. The goal in plain words. What breaks or gets worse without it.
16
+ Who benefits. Every claim carries its reason. No chains of story IDs.
17
+ At `senior`, also the trade-offs and the alternatives that were considered.>
18
+
19
+ ## The big picture
20
+ <2–3 sentences naming the flow, then the diagram. The diagram never replaces them.>
21
+
22
+ ```mermaid
23
+ flowchart LR
24
+ A["<part>"] -->|"<what flows>"| B["<part>"]
25
+ ```
26
+
27
+ `<part> → <part> → <part>`
28
+
29
+ <Then the main walk-through as a sequence diagram, including the failure branch.>
30
+
31
+ ```mermaid
32
+ sequenceDiagram
33
+ participant A as <part>
34
+ participant B as <part>
35
+ A->>B: <what is sent>
36
+ alt <the thing that can fail> fails
37
+ B->>A: <what happens instead>
38
+ else it succeeds
39
+ B-->>A: <the happy result>
40
+ end
41
+ ```
42
+
43
+ ## System design
44
+ <The components that exist today, how they connect, where this feature plugs in,
45
+ and what is planned next. If no architecture doc exists, derive it from the code
46
+ and say that is what you did.>
47
+
48
+ ```mermaid
49
+ flowchart TD
50
+ A["<component>"] -->|"<what flows>"| B["<component>"]
51
+ C["<future component>"] -.->|"planned in story <n>"| B
52
+ classDef current fill:#fff3bf,stroke:#f08c00
53
+ class B current
54
+ ```
55
+
56
+ `<component> → <component> → <component>`
57
+
58
+ ## The parts, one by one
59
+
60
+ ### <Part, by its real name>
61
+ **In general** — <1–2 plain sentences: what this kind of thing is and how it works
62
+ anywhere, with the usual alternative for contrast, such as a webhook against polling,
63
+ a queue against a direct call, a worker against doing the work inside the request.
64
+ At `zero`, one everyday analogy here.>
65
+ **In this project** — <file, function, config, real names, and how a message actually
66
+ flows through it here.>
67
+ **How it connects** — <what feeds it and what it feeds, today, and what is planned next.>
68
+ **Example** — <one concrete walk-through with real values.>
69
+ **❌ Doing it wrong** — <a realistic mistake on this part and what breaks because of it.>
70
+ **✅ Doing it right** — <the correct approach and why it works.>
71
+
72
+ ## Common mistakes on this feature
73
+ - <mistake> → <the symptom you would see> → <the fix>
74
+ <3 to 5 of these.>
75
+
76
+ ## Check yourself
77
+ 1. <question>
78
+ 2. <question>
79
+ 3. <question>
80
+
81
+ <details>
82
+ <summary>Answers</summary>
83
+
84
+ 1. <answer>
85
+ 2. <answer>
86
+ 3. <answer>
87
+
88
+ </details>
89
+
90
+ ## Glossary
91
+ - **<term>** — <one plain line>. <For a component, the three layers in one line each.>
92
+
93
+ <Omitted entirely at `senior`.>
94
+
95
+ ## Next step
96
+ <One line: where the task resumes.>
97
+ ````
98
+
99
+ ## Diagram rules
100
+
101
+ - At most 10 nodes per diagram. If it needs more, the diagram is at the wrong altitude.
102
+ - Every edge in the big-picture diagram is labelled with **what flows**, not with a verb alone.
103
+ - Use the pipe form for labels, `A -->|"text"| B`, and quote every label. Dotted edges for planned connections, `A -.->|"planned in story 5.8"| B`.
104
+ - Node labels use plain words the reader has already met. A diagram never introduces a new term.
105
+ - One ASCII fallback line goes directly under each `flowchart` block, for readers whose viewer has no Mermaid.
106
+ - A diagram never replaces the sentence that explains it.
107
+ - Style the current feature's parts in the system-design diagram with `classDef current fill:#fff3bf,stroke:#f08c00` and a matching `class` line.
108
+ - Render labels in the user's language.
109
+ - Keep the syntax conservative and re-read every block before finishing. Nothing renders it here, so a typo ships.
110
+ - Use two small `flowchart` blocks for ❌ against ✅ only when the difference is structural, such as a retry loop with and without an idempotency key. Otherwise prose is better.
111
+
112
+ ## By level
113
+
114
+ | Level | Glossary | Analogies | "Why this exists" | ❌/✅ examples |
115
+ |---|---|---|---|---|
116
+ | `dev` | yes | where they help | goal and consequences | realistic implementation mistakes |
117
+ | `zero` | expanded, every term | one per part, in "In general" | goal and consequences, extra plain | beginner mistakes |
118
+ | `senior` | none | none | plus trade-offs and alternatives considered | design choices, such as at-least-once against exactly-once |
119
+
120
+ ## Worked excerpt
121
+
122
+ From `docs/dumb/2026-09-22-retry-webhook-backoff.md`, the why and one part.
123
+
124
+ ````markdown
125
+ ## Why this exists
126
+
127
+ Epic 5 promises that a confirmed payment always becomes a paid order, because a
128
+ customer whose money left their account and whose order still says "pending" will
129
+ charge back and stop trusting the shop. Today the payment provider tells us once,
130
+ and `handlePaymentWebhook` processes it immediately. If our order service is down
131
+ for that one second, the message is gone and nobody ever retries, so the payment is
132
+ lost silently. Story 5.4 already made reprocessing safe to repeat, which is what
133
+ makes an automatic retry possible at all. This story adds the place to keep a failed
134
+ message and the schedule for trying it again.
135
+
136
+ ### handlePaymentWebhook
137
+ **In general** — A webhook is one system calling another to announce that something
138
+ happened, so the receiver does not have to ask repeatedly. The usual alternative is
139
+ polling, where you ask "is it paid yet?" on a timer, which wastes calls and still
140
+ adds delay.
141
+ **In this project** — It is the exported function in `src/payments/webhook.ts`. The
142
+ provider posts the confirmation to it, and it calls `processOrder` straight away.
143
+ **How it connects** — The payment provider feeds it, and it feeds `processOrder`
144
+ today. After this story it also feeds the `webhook_retries` table whenever
145
+ `processOrder` throws, and the retry job picks up from there.
146
+ **Example** — A payment clears, the provider posts `{"id":"pay_123","status":"paid"}`,
147
+ the function calls `processOrder` with it, and the order flips to paid.
148
+ **❌ Doing it wrong** — Catching the error and returning 200 without storing anything.
149
+ The provider sees success and never sends the message again, so the payment is lost
150
+ with no trace to debug.
151
+ **✅ Doing it right** — Write the payload to `webhook_retries` before returning, because
152
+ the row is the only evidence that the payment arrived, and the retry job has nothing
153
+ to work from without it.
154
+ ````
@@ -1,82 +1,115 @@
1
- # Levels — templates and a worked example
1
+ # Levels — the chat reply and the dialogue
2
+
3
+ The depth goes in the document ([document.md](document.md)). The chat stays short: at most 100 words plus the path. Never paste the document into the chat.
2
4
 
3
5
  Headers below are shown in English, the canonical form; render them in the user's language.
4
6
 
5
- ## `dev` (default) — about 250 words
7
+ ## The chat reply, every level except `terms`
6
8
 
7
9
  ```
8
- 🧠 What we're doing
9
- <1–2 sentences. Names the story / file / function / command being touched right now.>
10
+ <3–5 short lines: what we're doing and why it matters, plain words,
11
+ one new technical term per sentence at most.>
10
12
 
11
- 🎯 Why (the bigger picture)
12
- <At most 4 sentences: the goal this serves; where it sits (epic, roadmap,
13
- dependency chain); what breaks or gets harder without it; why now and not later.>
13
+ 📄 <path to the file that was written>
14
+ Read the whole document first. When you are done, say so and we go on from there.
15
+ ```
14
16
 
15
- 🪄 Analogy
16
- <2–3 sentences. One real-world analogy, each part mapped to one part of this step.>
17
+ At `senior` the last line is omitted. This first reply never asks a question and never offers terms: the user has not read the document yet. The level's ending below is sent only after the user says they have read it ("li", "read it", "done").
17
18
 
18
- 👀 Senior's eye
19
- <2 bullets, one sentence each: trade-offs or pitfalls a senior would watch for right here.>
19
+ ## `dev` (default) — ending, after the user has read the document
20
20
 
21
- 🤔 One question
22
- <One question the user can answer to check they got it. Omit if it would be forced.>
21
+ One line, nothing more:
23
22
 
24
- ➡️ Next step: <one line>
25
23
  ```
26
-
27
- ## `zero` — about 300 words, the analogy carries the explanation
28
-
24
+ Want me to explain <term>, <term> or <term>, or shall we continue?
29
25
  ```
30
- 🪄 Analogy
31
- <3–4 sentences. The real-world analogy first; the rest of the answer refers back to it.>
32
26
 
33
- 🧠 What we're doing
34
- <2–3 sentences. The step, told through the analogy; every technical word appears as
35
- "term (plain-words meaning)" the first time.>
27
+ If the user says continue, say nothing further and continue the task. If the user picks a term, answer it in chat under the dialogue rules below.
36
28
 
37
- 🎯 Why (the bigger picture)
38
- <2–3 sentences: what goes wrong without it, in the analogy's terms, then in the
39
- project's terms. On a BMAD story this slot still names the epic, the stories it
40
- builds on and the story it blocks — in plain words, not numbers alone.>
29
+ ## `zero` — ending, after the user has read the document
41
30
 
42
- 🤔 One question
43
- <One question answerable from the analogy.>
31
+ Aliases `eli5`, `beginner`, `junior`. Sent when the user says they have read it, even if that message also says "continue" (at this level the check is the gate back to the task); the reply ends here and **the turn stops**. Do not explain the offered terms yet. The task resumes after the user answers, or after they say they would rather skip the check.
44
32
 
45
- ➡️ Next step: <one line>
33
+ ```
34
+ 🤔 Before we go on:
35
+ 1. Want me to explain any of these? <term> · <term> · <term>
36
+ 2. Quick question: <one simple comprehension question about this step>
46
37
  ```
47
38
 
48
- No `👀 Senior's eye` section at this level.
39
+ ## `senior` — ending
49
40
 
50
- ## `terms` — 3 to 8 items, no analogy
41
+ Aliases `pro`, `expert`. Nothing, not even the "read it first" line. No question, no offer to define terms. The document carries the trade-offs.
42
+
43
+ ## `terms` — chat only, no file
44
+
45
+ Aliases `termos`, `jargon`, `glossary`.
51
46
 
52
47
  ```
53
48
  📚 Terms in this step
54
- - **<term>** — <one-sentence meaning>. Here: <one concrete example from this task>.
49
+ - **<term>** — <one-sentence meaning, with the reason it exists>. Here: <one concrete example from this task>.
55
50
  - ...
56
51
 
57
52
  ➡️ Next step: <one line>
58
53
  ```
59
54
 
60
- ## Worked example (`dev`, BMAD story)
55
+ Three to eight items, no analogy.
56
+
57
+ ## Dialogue rules
58
+
59
+ - **A term the user picked** — about 80 words: one sentence of meaning, then how it works in this project naming a real file or function, then one tiny example. Then, at `zero` only, one new question.
60
+ - **A wrong answer to the check question** — do not repeat the same explanation. Explain it a different and simpler way, then ask one new question.
61
+ - **"entendi" / "ok" / "got it"** — close with `➡️ Next step` and nothing else.
62
+ - Never offer a term the user already knows or already had explained.
63
+ - Stop asking the moment the user turns back to the task.
64
+
65
+ ## Worked example — `dev` chat reply
66
+
67
+ Situation: story 5.6, the `webhook_retries` migration was just written, the user asks "why are we doing this story?".
68
+
69
+ ```
70
+ We just added the table that remembers a payment confirmation we failed to
71
+ process, so a retry can pick it up later. Without it, one second of downtime in
72
+ the order service loses a real payment, because the provider only tells us once.
73
+ I wrote the whole thing up, parts and diagrams included.
74
+
75
+ 📄 docs/dumb/2026-09-22-retry-webhook-backoff.md
76
+ Read the whole document first. When you are done, say so and we go on from there.
77
+ ```
61
78
 
62
- Situation: story 5.6 "Retry with backoff for webhook delivery", the migration `webhook_retries` was just written, the user asks "why are we doing this story?".
79
+ The user replies "read it". Only now the offer line:
63
80
 
64
81
  ```
65
- 🧠 What we're doing
66
- We wrote the `webhook_retries` migration for story 5.6: the table holding every failed payment webhook and when to retry it.
82
+ Want me to explain webhook, idempotency or dead letter, or shall we continue?
83
+ ```
67
84
 
68
- 🎯 Why (the bigger picture)
69
- 5.6 belongs to Epic 5 (Payments): "a confirmed payment becomes a paid order, always". 5.3 (webhook) and 5.4 (idempotency) guarantee the confirmation arrives and is not processed twice, but not that it arrives at all if the order processor is down that second. Without retry a real payment vanishes, which is why 5.7 (status screen) is blocked: you cannot show "paid" for something that can disappear. AC 2 fixes the shape: 1m → 5m → 25m → 2h, 5 attempts.
85
+ ## Worked example — `zero` chat reply, then one follow-up
70
86
 
71
- 🪄 Analogy
72
- A tracked parcel: if nobody answers, the carrier does not bin it; they leave a notice and come back at longer and longer intervals. After 5 attempts it goes to the depot (dead letter) and someone collects it.
87
+ ```
88
+ We created a table called `webhook_retries`. It stores the payment confirmations
89
+ our system failed to handle the first time, so we can try again later. Without
90
+ it, the money leaves the customer and the order never becomes paid.
73
91
 
74
- 👀 Senior's eye
75
- - `next_attempt_at` needs an index: the job asks "what is due?" every minute.
76
- - Retry without 5.4's idempotency = double payment; reprocessing goes down the same path.
92
+ 📄 docs/dumb/2026-09-22-retry-webhook-backoff.md
93
+ Read the whole document first. When you are done, say so and we go on from there.
94
+ ```
77
95
 
78
- 🤔 One question
79
- Why growing intervals instead of retrying every minute?
96
+ The user replies "done, read it". Only now the check block:
97
+
98
+ ```
99
+ 🤔 Before we go on:
100
+ 1. Want me to explain any of these? webhook · idempotency · dead letter
101
+ 2. Quick question: if we never saved that message, what would happen to the
102
+ customer's order?
103
+ ```
104
+
105
+ The user replies "I don't know what a webhook is". Only that term is answered, then one new question:
106
+
107
+ ```
108
+ A webhook is one system calling another to announce that something just happened,
109
+ so we never have to keep asking. Here the payment provider calls our
110
+ `handlePaymentWebhook` function in `src/payments/webhook.ts` the moment a payment
111
+ clears. It arrives once, which is exactly why losing it is expensive.
80
112
 
81
- ➡️ Next step: the job that reads `next_attempt_at <= now()`.
113
+ 🤔 One more: that function calls `processOrder` straight away. What happens today
114
+ if `processOrder` fails?
82
115
  ```