DDT stands for dont-do-that packaging.
This skill is used to remove wrapper layers that provide no real benefit, bringing code closer to the human reading path. The goal is not "the fewer the better", but to judge whether a layer of functions, types, constants, files, or parameter passing truly reduces the cost of understanding.
Read the project conventions first
If you need to review or modify project code, first read the CLAUDE.md in the repository and the existing AGENTS.md, then read the documentation files next to the relevant modules. When judging, consider the project conventions, the user's long-term constraints, and the current code at the same time; do not draw conclusions based only on DDT rules.
If the project conventions conflict with DDT, prioritize the more specific project conventions; at the same time, point out that this will retain some structures that look thin. Type file naming and placement should also be handled according to the project's type conventions first; only when the project has no explicit type file constraints should types extracted from business TS source files be placed in the same directory {源文件名}.d.ts。
Choose reference materials
Read the corresponding reference materials according to the current task; do not load irrelevant content all at once.
- React components, React hooks,
.tsx, component-associatedutilfiles, frontend state chains: readreferences/react/index.md。 - Node.js backend, service / query / logic, database reads and writes, Kysely, interface DTOs, backend types and enums: read
references/nodejs/index.md。 - Purely general issues, such as small functions, thin types, single-point constants, instance property parameter bypassing: just use this file.
Core judgment
First judge whether the split has real benefit, then decide whether to keep or remove it.
看到一层函数 / 类型 / 常量 / 文件 / 参数传递 / 兜底分支
│
▼
它是否被多个地方稳定复用?
├─ 是 ──► 保留,并确认命名和注释能表达业务语义
│
└─ 否
│
▼
它是否明显降低主流程认知负担?
├─ 是 ──► 保留,但避免继续拆更薄的层
│
└─ 否
│
▼
它是否表达独立业务边界、外部协议、测试边界或项目强约束?
├─ 是 ──► 保留,并让边界更清楚
│
└─ 否 ──► 收回:内联函数、合并类型、删除透传参数、中间层或重复兜底
General process
开始 DDT 检查
│
▼
读取 CLAUDE.md / AGENTS.md / 模块说明
│
▼
按文件类型选择引用资料
├─ React / TSX / hook ──► references/react/index.md
├─ Node.js 后台 ────────► references/nodejs/index.md
└─ 通用结构问题 ───────► 留在 SKILL.md
│
▼
列出候选包装层
│
▼
逐项判断收益:复用、主流程、边界、测试、项目规范
│
▼
采取最小动作
├─ 无收益 ──► 内联 / 合并 / 删除绕行 / 收回单点常量
└─ 有收益 ──► 保留,并改善命名、注释或放置位置
│
▼
更新 import、类型引用和必要注释
│
▼
运行最小必要验证并报告结果
General rules
If a function has only a few lines, has only one caller, and the function name does not provide new business semantics, by default this is over-encapsulation and should be inlined back into the call site. Do not keep such thin functions just to look more modular.
If a type is just a thin slice cut out of another type, does not form stable reuse, and does not significantly reduce the cost of understanding, by default merge it back into the more direct type definition. Only keep the split when the subtype is truly used independently in multiple places, or when extracting it clearly reduces the complexity of the main type.
If the business .ts / .tsx source file really needs to extract types from the main file, first read the project type file rules, existing patterns in the same directory, and upper-layer module conventions. When the project has rules, follow the project rules, for example types.ts、<api-file>.types.ts、src/types/*.d.ts or other agreed locations; only when the project has no rules should you use the same directory {源文件名}.d.ts, for example ChatComponent.tsx corresponds to ChatComponent.d.ts,agent.service.ts corresponds to agent.service.d.ts. Do not add a generalized type collection file that conflicts with project rules for a single source file.
If an enum,const、as const object, delay time, threshold, fixed key, mode, status, or tool name has only one usage site, by default couple it directly to the usage site. Only extract it to enum/** or a constants file when it is reused across multiple files, multiple branches, multiple business actions, or truly expresses an external protocol / complex business boundary.
An instance state that already exists stably inside the class should not be passed along a private method chain just to "explicitly pass parameters." Private methods are originally instance behavior; directly reading this.xxx is often better than const xxx = this.xxx; this.a(xxx); this.b(xxx); at expressing that this value belongs to object state.
Keep fallback logic only when the input comes from an unstable external boundary, or when the lack of a fallback would cause a clear error. When the state is already guaranteed by the lifecycle, event order, state machine, or type definitions, do not keep redundant defensive branches just to be "safer."
Code first serves human reading, not formal decoupling. Local slight coupling is usually easier to read than jumping through one extra layer.
Decision table
| Object | Tend to retract | Tend to keep |
|---|---|---|
| Small function | 2 to 5 lines, single caller, function name restates the implementation | Stable reuse in multiple places, expresses a business action, needs independent testing |
| Type | Just a layer of aliasing, extracting fields, very narrow consumption scope | External contract, message body, event payload, stable nested structure |
| Constant / enum | Only one reference, the context is already self-explanatory | Reused in multiple places, external protocol, complex business boundary |
| Parameter | The caller only reads from this and passes it to a private method |
The parameter comes from the call site, or changes on each call |
| Fallback | Already guaranteed by the state machine, lifecycle, or type not to trigger | External input is unstable, or the lack of protection would cause an error |
Execute actions
When over-wrapping is confirmed, do these things directly.
| Problem | Action |
|---|---|
| Small function with a single caller | Inline it back to the call site, delete the helper |
| Intermediate layer with no benefit | Delete the pass-through layer, make the call relationship shorter |
| Overly thin type | Merge back into the main type or a more direct boundary type |
| Types that need to be stripped from the business TS source file | First follow the project type rules; when there are no constraints, use the same-directory {源文件名}.d.ts |
| Enum or constant with a single reference | Inline it back to the usage site, delete the enum / const |
| Instance property parameter that is merely passed through | Have the target private method directly read this.xxx |
| Fallback branch that the main flow already guarantees will not trigger | Delete the duplicate defense |
| Necessary comments obscured by thin encapsulation | Keep the comments, but explain only the key intent |
After modification, check imports, circular references, type imports, JSDoc, and the project-required minimum validation. Choose the validation scope according to the impact surface, covering at least lint or type checking for the affected files.
Output requirements
If the user asks for a direct fix, complete the modification and validation before replying with the result.
If the user asks for a review, point out by file which functions, types, constants, or file responsibilities are DDT problems, and give the minimal modification suggestions. Do not make general comments about "abstraction level."
When explaining the reason, a short sentence is enough, for example, "there is only one caller and the logic is too thin, so inlining makes the reading path shorter."