The history of a single AI conversation is a history of corruption: AI writes code too easily, too bloatedly, too prone to divergence; whether written by humans or machines, code will eventually corrupt. This methodology turns "anti-corruption" into a repeatable process:Evidence-driven subtraction audit + risk-tiered execution + machine gates。
Core Principles (read first, never skip)
- Gates > Prose: conventions must become executable checks (non-zero exit code means failure), otherwise Agents will not comply. AI's compliance with enforced gates is far higher than with prose-style conventions.
- Tests are not truth: tests pin down current behavior, not correct behavior—behavior may be the product of historical compromises. A test must be able to fail for the mechanism you report, otherwise it is lying.
- Deletion is a first-class citizen: simplification is a routine workflow, not a spring cleaning. Zero deleted lines per month = corruption alarm.
- Decisions must be written down: every non-trivial deletion gets a decision record (Problem / Evidence / Cost / Rollback Conditions).
- Evidence first: candidates without consumption evidence are never reported. "Looks useless" is not evidence; rg is.
When to use
- The user wants to "clean up the repository", "find dead code", "can these be deleted"
- The user is worried that AI-written code is bloated/divergent/unmaintainable
- The user wants to set up quality gates for a repository (especially one primarily developed by AI)
Workflow
Phase 0 · Understand the architecture first (most important, most prone to failure)
Before claiming anything is "dead", answer these first—skipping this step is equivalent to building your report on a foundation:
- Where are the entry points? How are routes/endpoints mounted? (static import / dynamic require / framework auto-discovery such as Next.js pages / string paths)
- Is there asame-named live file? (Example:
routes/agent/feedback.tsis suspected dead, butroutes/feedback.tsis live and mounted at/api/feedback—two different files) - How do build/deploy describe it? (docker-compose, deploy manifest, runbook, README claims vs reality)
- Is there anything that looks like a "placeholder/skeleton" but is actuallyproduction core? (Small packages may be queue consumers, workers, schedulers)
- Are environment variables/config knobs referenced externally (.env.example, deployment platform env, container orchestration)
- Are database tables / queues / event names referenced by strings
Counterexample lesson (real incident): a 101-line "worker skeleton" was actually a production-required DB queue consumer (polling the generation_tasks table). It not being in the deploy manifest ≠ it is dispensable—that may beconfiguration drift, it may even mean the production queue has stalled. Do not treat "not in the deploy config" as "not needed".
Phase 1 · Baseline measurement
- Dead code baseline:
npx knip --reporter json(unused exports / types / files / dependencies / duplicate implementations / binaries) - Repository hygiene: mistakenly committed files (whose commit message does not match the contents),
.gitignorecoverage, whether.envis tracked (a security item, must be checked), committed large artifacts/tool output, empty directories, dead gates (scripts that exist but are not wired into CI/hooks) - CI/hook inventory:
.github/, what husky pre-commit actually runs; whether lint/test/typecheck are executed by any automated process.Zero CI is the root cause of accumulated rot, and should be listed separately as a systemic issue - Record the baseline numbers — every subsequent cleanup should bring them down; they may only go down, never up
Phase 2 · Domain-parallel investigation
Dispatch parallel subagents by domain (api / web / worker / shared / root and scripts), with the same methodological prompt for each domain:
- Investigation categories: ① Dead code (zero production references; test-only references count too; annotate the evidence) ② Duplicate representations (two mirrors of the same fact: two objects mirroring the same set of fields, hand-written types coexisting with generated types) ③ Hand-rolled vs mature dependencies (protocol parsing/retry/glob/diff/throttling — replaceable by mature npm packages or Node built-ins) ④ Speculative generalization (config knobs with no owner, placeholder services, large commented-out blocks, unused APIs maintained only to be protected by tests) ⑤ Dead dependencies (present in package.json, zero imports in source)
- Iron rule: for every candidate, first search the entire repository for consumers,distinguishing production / test / documentationthree types of consumption; report nothing without evidence; at the end you must provide an "intentionally excluded" list (those verified as live/intentionally retained)
Phase 3 · High-risk review (mandatory before deletion)
Add a review pass for every candidate, especially api/web routes and public exports:
- rg across the entire repository,including dynamic references and string paths(
import.meta.glob、require(、.route("字符串")) - Live file with the same namecheck (see the Phase 0 lesson)
- Framework auto-discoverycheck (Next.js pages/API routes are auto-registered by the framework — knip will produce false positives here; confirm whether the app uses framework routing or explicit routing such as React Router)
- Deployment/documentation reference check (deploy manifest, runbook, env)
- Whether a test pins down that it does not exist (e.g.
doesNotMatch(/<Component/)assertions) — that is supporting evidence for deletion
Phase 4 · Risk classification
| Level | Meaning | Action |
|---|---|---|
| 🟢 Green | Evidence loop closed (zero production + test + documentation consumption, no same-name/dynamic/framework references) | Safe to delete |
| 🟡 Yellow | Confirm before deleting (is there a runtime parse? Does the local implementation have its own copy? Does merging require a smoke test?) | Check first, then delete |
| 🔴 Red | Requires a product decision (are both implementation paths alive? Which one does production depend on? Is deployment affected?) | Separate work item; ask a human |
Phase 5 · Batch execution
- Each batch = one category of candidates (hygiene → dead files → dead exports → duplicate merging → dead dependencies); do not mix batches
- One decision record per batch (
DECISIONS.md: Problem / Evidence / Cost / Rollback condition) - Run verification after each batch:
tsc --noEmit(full package, not checking only half) + lint + knip baseline does not rise - Changes involving production components (queue consumers, workers, deployment-related, env knobs) must be smoke-tested after modification
Phase 6 · System fixes (more important than deleting code)
- Zero CI is the root cause: build minimal CI—lint + test + typecheck full package + knip baseline, fail means red
- Complete the hooks: pre-commit runs lint/typecheck (staged scope)
- Put "lines deleted" into the monthly review metrics: zero means alarm
Deliverable template (report structure)
- Rot inventory baseline (knip numbers)
- Systemic issues (CI/gates/deployment drift—list separately, more important than deleting code)
- P0 hygiene (accidental commits/artifacts/ignore) → P1 dead files → P2 dead exports → P3 duplicate implementations → P4 dead state → dead dependencies
- Security special items (whether .env is committed, secret scanning)
- Intentional exclusion list (verified as alive)
- Risk-based execution plan + list of questions that must be answered by the product
Common pitfalls
- Treating "not in the deployment manifest" as "not needed"—it may be configuration drift or a production outage (worker lesson)
- Being misled by knip false positives—framework auto-discovery, dynamic require, and string references all cause false positives, so manual review is mandatory
- Seeing only the trees—deleting only obvious dead symbols, missing the places with the heaviest file-level duplicate lifecycle/defensive mechanisms (check: two files implementing the same withRetry, two parallel component trees)
- Deleting everything at once without verification—must batch + verify the baseline after each batch
- Not distinguishing production/test/documentation consumption—"referenced only by tests" is also dead code, but the evidence must be annotated
- Deleting 🔴-level candidates without asking product questions
Supporting templates
Sub-agent domain-splitting prompt skeleton(used in Phase 2):
你是代码简化审计员。审计目标:<路径>(<规模>)。只读,绝不修改文件、不跑构建。
方法:只报有证据的候选。每个候选先在**整个仓库**用 rg 搜消费方,区分"生产代码消费" vs "只有测试/文档消费"。没有消费证据的一律不报。
重点找:①死代码 ②重复表示 ③手搓 vs 成熟依赖 ④投机性泛化 ⑤死依赖。
产出:Markdown 表格,按置信度排序,最多 15 条。每列:候选(文件:行+符号)| 证据(rg 搜了什么)| 建议动作 | 置信度(高/中/低)。末尾列"有意排除的"。中文输出。
Decision record template(used in Phase 5):
# DECISION: <删除/合并/降级> <对象>
## Problem
<当前 API/文件/依赖是什么,消费证据(生产 N 处 / 仅测试 / 仅文档)>
## 证据
<rg 命令与结果;同名/动态/框架引用检查结果>
## 代价 / 我们放弃什么
<最强的反对理由;未来可能需要的场景>
## 回退条件
<什么情况下要恢复>
## 验证
<tsc / lint / knip 基线对比;冒烟范围>
References
- Methodology originates from DeepSeek Harness:
dsh-find-simplificationsskill, AGENTS.md conventions, 144 simplification-type Agent Notes, quality gate records, postmortem 0002/0003 (tests are not the truth) - Real-world case: berryon subtraction audit (4 domains in parallel + knip baseline + risk grading), lessons have been embedded in this document